better CLI argument handling, some small fixes

This commit is contained in:
Niklas Gollenstede 2022-08-31 08:38:33 +02:00
parent 7ab9215b0a
commit 1d93a8acc0
12 changed files with 121 additions and 54 deletions

View File

@ -81,7 +81,7 @@ in rec {
inherit name; nodes = peers; # NixOPS
};
in let system = { inherit preface; } // (nixosSystem {
system = targetSystem;
system = buildSystem;
modules = [ (
(importWrapped inputs entryPath).module
) {
@ -101,10 +101,10 @@ in rec {
}; })));
apply = lib.filterAttrs (k: v: v != null);
}; config.${prefix}.setup.scripts = lib.mapAttrs (name: path: lib.mkOptionDefault { inherit path; }) (setup-scripts);
} ({ config, pkgs, ... }: {
} ({ config, options, pkgs, inputs ? { }, ... }: {
options.${prefix}.setup.appliedScripts = lib.mkOption {
type = lib.types.functionTo lib.types.str; readOnly = true;
default = context: substituteImplicit { inherit pkgs; scripts = lib.sort (a: b: a.order < b.order) (lib.attrValues config.${prefix}.setup.scripts); context = system // context; }; # inherit (builtins) trace;
default = context: substituteImplicit { inherit pkgs; scripts = lib.sort (a: b: a.order < b.order) (lib.attrValues config.${prefix}.setup.scripts); context = { inherit config options pkgs inputs preface; } // context; }; # inherit (builtins) trace;
};
}) ({ config, ... }: {
@ -149,7 +149,7 @@ in rec {
throw "»${prefix}.preface.id«s are not unique! The following hosts share their IDs with some other host: ${builtins.concatStringsSep ", " (builtins.attrNames duplicate)}"
) else configs;
# Builds a system of NixOS hosts and exports them plus managing functions as flake outputs.
# 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.
@ -170,8 +170,10 @@ in rec {
nixosSystem ? inputs.nixpkgs.lib.nixosSystem,
# If provided, then cross compilation is enabled for all hosts whose target architecture is different from this. Since cross compilation currently fails for (some stuff in) NixOS, better don't set »localSystem«. Without it, building for other platforms works fine (just slowly) if »boot.binfmt.emulatedSystems« on the building system is configured for the respective target(s).
localSystem ? null,
## If provided, then change the name of each output attribute by passing it through this function. Allows exporting of multiple variants of a repo's hosts from a single flake:
renameOutputs ? null,
... }: let
otherArgs = (builtins.removeAttrs args [ "systems" ]) // { inherit inputs systems overlays modules specialArgs nixosSystem localSystem; };
otherArgs = (builtins.removeAttrs args [ "systems" "renameOutputs" ]) // { inherit inputs systems overlays modules specialArgs nixosSystem localSystem; };
nixosConfigurations = if builtins.isList systems then mergeAttrsUnique (map (systems: mkNixosConfigurations (otherArgs // systems)) systems) else mkNixosConfigurations (otherArgs // systems);
in let outputs = {
inherit nixosConfigurations;
@ -179,7 +181,7 @@ in rec {
pkgs = (import inputs.nixpkgs { inherit overlays; system = localSystem; });
tools = lib.unique (map (p: p.outPath) (lib.filter lib.isDerivation pkgs.stdenv.allowedRequisites));
PATH = lib.concatMapStringsSep ":" (pkg: "${pkg}/bin") tools;
in {
in rec {
# Do per-host setup and maintenance things:
# SYNOPSIS: nix run REPO#HOST [-- [sudo] [bash | -x [-c SCRIPT | FUNC ...ARGS]]]
@ -250,7 +252,14 @@ in rec {
${lib.concatStringsSep "\n" (lib.mapAttrsToList (name: { outPath, ... }: "ln -sT ${outPath} $out/inputs/${name}") inputs)}
''}
'';
checks.all-systems = packages.all-systems;
})); in outputs;
})); in if renameOutputs == null then outputs else {
nixosConfigurations = mapMerge (k: v: { ${renameOutputs k} = v; }) outputs.nixosConfigurations;
} // (forEachSystem [ "aarch64-linux" "x86_64-linux" ] (localSystem: {
apps = mapMerge (k: v: { ${renameOutputs k} = v; }) outputs.apps.${localSystem};
devShells = mapMerge (k: v: { ${renameOutputs k} = v; }) outputs.devShells.${localSystem};
packages.${renameOutputs "all-systems"} = outputs.packages.${localSystem}.all-systems;
checks.${renameOutputs "all-systems"} = outputs.checks.${localSystem}.all-systems;
}));
}

View File

@ -41,7 +41,7 @@ function gen-key-constant {( set -eu # 1: _, 2: value
## Obtains a key by prompting for a password.
function gen-key-password {( set -eu # 1: usage
usage=$1
( prompt-new-password "as key for @{config.networking.hostName}/$usage" || exit 1 )
( prompt-new-password "as key for @{config.networking.hostName}:$usage" || exit 1 )
)}
## Generates a key by prompting for (or reusing) a »$user«'s password, combining it with »$keystore/home/$user.key«.
@ -50,7 +50,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 "that will be used as component of the key for @{config.networking.hostName}/$usage")
password=$(prompt-new-password "that will be used as component of the key for »@{config.networking.hostName}:$usage«")
if [[ ! $password ]] ; then exit 1 ; fi
fi
( cat "$keystore"/home/"$user".key && cat <<<"$password" ) | sha256sum | head -c 64
)}
@ -63,16 +64,18 @@ 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 "as YubiKey challenge for @{config.networking.hostName}/$usage")
password=$(prompt-new-password "as YubiKey challenge for »@{config.networking.hostName}:$usage«")
if [[ ! $password ]] ; then exit 1 ; fi
fi
gen-key-yubikey-challenge "$usage" "$serial:$slot:home-$password" true "${user}'s password for ${usage}"
gen-key-yubikey-challenge "$usage" "$serial:$slot:home-$user=$password" true "»${user}«'s password (for key »${usage}«)"
)}
## Generates a reproducible secret by prompting for a pin/password and then challenging slot »$slot« of YubiKey »$serial«.
function gen-key-yubikey-pin {( set -eu # 1: usage, 2: serialAndSlot(as »serial:slot«)
usage=$1 ; serialAndSlot=$2
password=$(prompt-new-password "/ pin as challenge to YubiKey »$serialAndSlot« as key for @{config.networking.hostName}/$usage")
gen-key-yubikey-challenge "$usage" "$serialAndSlot:$password" true "pin for ${usage}"
password=$(prompt-new-password "/ pin as challenge to YubiKey »$serialAndSlot« as key for »@{config.networking.hostName}:$usage«")
if [[ ! $password ]] ; then exit 1 ; fi
gen-key-yubikey-challenge "$usage" "$serialAndSlot:$password" true "password / pin as key for »@{config.networking.hostName}:$usage«"
)}
## Generates a reproducible secret for a certain »$use«case and optionally »$salt« on a »$host« by challenging slot »$slot« of YubiKey »$serial«.
@ -91,19 +94,19 @@ function gen-key-yubikey-challenge {( set -eu # 1: _, 2: serialAndSlotAndChallen
serial=$( <<<"$args" cut -d: -f1 ) ; slot=$( <<<"$args" cut -d: -f2 )
challenge=${args/$serial:$slot:/}
if [[ "$serial" != "$(@{native.yubikey-personalization}/bin/ykinfo -sq)" ]] ; then printf 'Please insert / change to YubiKey with serial %s!\n' "$serial" 1>&2 ; fi
if [[ "$serial" != "$( @{native.yubikey-personalization}/bin/ykinfo -sq )" ]] ; then printf 'Please insert / change to YubiKey with serial %s!\n' "$serial" 1>&2 ; fi
if [[ ! "${3:-}" ]] ; then
read -p 'Challenging YubiKey '"$serial"' slot '"$slot"' twice with '"${message:-challenge »"$challenge":1/2«}"'. Enter to continue, or Ctrl+C to abort:'
else
read -p 'Challenging YubiKey '"$serial"' slot '"$slot"' once with '"${message:-challenge »"$challenge"«}"'. Enter to continue, or Ctrl+C to abort:'
fi
if [[ "$serial" != "$(@{native.yubikey-personalization}/bin/ykinfo -sq)" ]] ; then printf 'YubiKey with serial %s not present, aborting.\n' "$serial" 1>&2 ; exit 1 ; fi
if [[ "$serial" != "$( @{native.yubikey-personalization}/bin/ykinfo -sq )" ]] ; then printf 'YubiKey with serial %s not present, aborting.\n' "$serial" 1>&2 ; exit 1 ; fi
if [[ ! "${3:-}" ]] ; then
secret="$(@{native.yubikey-personalization}/bin/ykchalresp -"$slot" "$challenge":1)""$(@{native.yubikey-personalization}/bin/ykchalresp -2 "$challenge":2)"
secret="$( @{native.yubikey-personalization}/bin/ykchalresp -"$slot" "$challenge":1 )""$( sleep .5 || : ; @{native.yubikey-personalization}/bin/ykchalresp -"$slot" "$challenge":2 || @{native.yubikey-personalization}/bin/ykchalresp -"$slot" "$challenge":2 )" # the second consecutive challenge tends to fail if it follows immediately
if [[ ${#secret} != 80 ]] ; then printf 'YubiKey challenge failed, aborting.\n' "$serial" 1>&2 ; exit 1 ; fi
else
secret="$(@{native.yubikey-personalization}/bin/ykchalresp -"$slot" "$challenge")"
secret="$( @{native.yubikey-personalization}/bin/ykchalresp -"$slot" "$challenge" )"
if [[ ${#secret} != 40 ]] ; then printf 'YubiKey challenge failed, aborting.\n' "$serial" 1>&2 ; exit 1 ; fi
fi
printf %s "$secret" | head -c 64

View File

@ -36,7 +36,8 @@ function do-disk-setup { # 1: diskPaths
## Partitions the »diskPaths« instances of all »config.wip.fs.disks.devices« to ensure that all specified »config.wip.fs.disks.partitions« exist.
# Parses »diskPaths«, creates and loop-mounts images for non-/dev/ paths, and tries to abort if any partition already exists on the host.
function partition-disks { { # 1: diskPaths
beQuiet=/dev/null ; if [[ ${args[debug]:-} ]] ; then beQuiet=/dev/stdout ; fi
beLoud=/dev/null ; if [[ ${args[debug]:-} ]] ; then beLoud=/dev/stdout ; fi
beSilent=/dev/stderr ; if [[ ${args[quiet]:-} ]] ; then beSilent=/dev/null ; fi
declare -g -A blockDevs=( ) # this ends up in the caller's scope
local path ; for path in ${1//:/ } ; do
local name=${path/=*/} ; if [[ $name != "$path" ]] ; then path=${path/$name=/} ; else name=primary ; fi
@ -75,26 +76,28 @@ function partition-disks { { # 1: diskPaths
if [[ ${disk[serial]} != "$actual" ]] ; then echo "Block device $blockDev's serial ($actual) does not match the serial (${disk[serial]}) declared for ${disk[name]}" ; exit 1 ; fi
fi
# can (and probably should) restore the backup:
( PATH=@{native.gptfdisk}/bin ; set -x ; sgdisk --zap-all --load-backup=@{config.wip.fs.disks.partitioning}/"${disk[name]}".backup ${disk[allowLarger]:+--move-second-header} "${blockDevs[${disk[name]}]}" >$beQuiet )
( PATH=@{native.gptfdisk}/bin ; ${_set_x:-:} ; sgdisk --zap-all --load-backup=@{config.wip.fs.disks.partitioning}/"${disk[name]}".backup ${disk[allowLarger]:+--move-second-header} "${blockDevs[${disk[name]}]}" >$beLoud 2>$beSilent )
#partition-disk "${disk[name]}" "${blockDevs[${disk[name]}]}"
done
@{native.parted}/bin/partprobe "${blockDevs[@]}"
@{native.parted}/bin/partprobe "${blockDevs[@]}" &>$beLoud
@{native.systemd}/bin/udevadm settle -t 15 || true # sometimes partitions aren't quite made available yet
# ensure that filesystem creation does not complain about the devices already being occupied by a previous filesystem
wipefs --all "@{config.wip.fs.disks.partitions!attrNames[@]/#/'/dev/disk/by-partlabel/'}" >$beQuiet
wipefs --all "@{config.wip.fs.disks.partitions!attrNames[@]/#/'/dev/disk/by-partlabel/'}" >$beLoud 2>$beSilent
)}
## Given a declared disk device's »name« and a path to an actual »blockDev« (or image) file, partitions the device as declared in the config.
function partition-disk {( set -eu # 1: name, 2: blockDev, 3?: devSize
name=$1 ; blockDev=$2
beLoud=/dev/null ; if [[ ${args[debug]:-} ]] ; then beLoud=/dev/stdout ; fi
beSilent=/dev/stderr ; if [[ ${args[quiet]:-} ]] ; then beSilent=/dev/null ; fi
eval 'declare -A disk='"@{config.wip.fs.disks.devices[$name]}"
devSize=${3:-$( @{native.util-linux}/bin/blockdev --getsize64 "$blockDev" )}
declare -a sgdisk=( --zap-all ) # delete existing part tables
if [[ ${disk[gptOffset]} != 0 ]] ; then
sgdisk+=( --move-main-table=$(( 2 + ${disk[gptOffset]} )) ) # this is incorrectly documented as --adjust-main-table in the man pages (at least versions 1.05 to 1.09 incl)
sgdisk+=( --move-backup-table=$(( devSize/512 - 1 - 32 - ${disk[gptOffset]} )) )
sgdisk+=( --move-backup-table=$(( devSize/${disk[sectorSize]} - 1 - 32 - ${disk[gptOffset]} )) )
fi
sgdisk+=( --disk-guid="${disk[guid]}" )
@ -117,7 +120,7 @@ function partition-disk {( set -eu # 1: name, 2: blockDev, 3?: devSize
sgdisk+=( --hybrid "${disk[mbrParts]}" ) # --hybrid: create MBR in addition to GPT; ${disk[mbrParts]}: make these GPT part 1 MBR parts 2[3[4]]
fi
( PATH=@{native.gptfdisk}/bin ; set -x ; sgdisk "${sgdisk[@]}" "$blockDev" >$beQuiet ) # running all at once is much faster
( PATH=@{native.gptfdisk}/bin ; ${_set_x:-:} ; sgdisk "${sgdisk[@]}" "$blockDev" >$beLoud ) # running all at once is much faster
if [[ ${disk[mbrParts]:-} ]] ; then
printf "
@ -126,7 +129,7 @@ function partition-disk {( set -eu # 1: name, 2: blockDev, 3?: devSize
# move the selected »mbrParts« to slots 1[2[3]] instead of 2[3[4]] (by re-creating part1 in the last sector, then sorting)
n;p;1 # new ; primary ; part1
$(( ($devSize/512) - 1)) # start (size 1sec)
$(( ($devSize/${disk[sectorSize]}) - 1)) # start (size 1sec)
x;f;r # expert mode ; fix order ; return
d;$(( (${#disk[mbrParts]} + 1) / 2 + 1 )) # delete ; part(last)
@ -137,7 +140,7 @@ function partition-disk {( set -eu # 1: name, 2: blockDev, 3?: devSize
${disk[extraFDiskCommands]}
p;w;q # print ; write ; quit
" | @{native.gnused}/bin/sed -E 's/^ *| *(#.*)?$//g' | @{native.gnused}/bin/sed -E 's/\n\n+| *; */\n/g' | tee >((echo -n '++ ' ; tr $'\n' '|' ; echo) 1>&2) | ( PATH=@{native.util-linux}/bin ; set -x ; fdisk "$blockDev" &>$beQuiet )
" | @{native.gnused}/bin/sed -E 's/^ *| *(#.*)?$//g' | @{native.gnused}/bin/sed -E 's/\n\n+| *; */\n/g' | tee >((echo -n '++ ' ; tr $'\n' '|' ; echo) 1>&2) | ( PATH=@{native.util-linux}/bin ; ${_set_x:-:} ; fdisk "$blockDev" &>$beLoud )
fi
)}
@ -154,7 +157,8 @@ function is-partition-on-disks {( set -eu # 1: partition, ...: blockDevs
## For each filesystem in »config.fileSystems« whose ».device« is in »/dev/disk/by-partlabel/«, this creates the specified file system on that partition.
function format-partitions {( set -eu
beQuiet=/dev/null ; if [[ ${args[debug]:-} ]] ; then beQuiet=/dev/stdout ; fi
beLoud=/dev/null ; if [[ ${args[debug]:-} ]] ; then beLoud=/dev/stdout ; fi
beSilent=/dev/stderr ; if [[ ${args[quiet]:-} ]] ; then beSilent=/dev/null ; fi
for fsDecl in "@{config.fileSystems[@]}" ; do
eval 'declare -A fs='"$fsDecl"
if [[ ${fs[device]} == /dev/disk/by-partlabel/* ]] ; then
@ -162,7 +166,7 @@ function format-partitions {( set -eu
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[@]}" ; exit 1 ; fi
else continue ; fi
( PATH=@{native.e2fsprogs}/bin:@{native.f2fs-tools}/bin:@{native.xfsprogs}/bin:@{native.dosfstools}/bin:$PATH ; set -x ; mkfs.${fs[fsType]} ${fs[formatOptions]} "${fs[device]}" >$beQuiet )
( PATH=@{native.e2fsprogs}/bin:@{native.f2fs-tools}/bin:@{native.xfsprogs}/bin:@{native.dosfstools}/bin:$PATH ; ${_set_x:-:} ; mkfs.${fs[fsType]} ${fs[formatOptions]} "${fs[device]}" >$beLoud 2>$beSilent )
@{native.parted}/bin/partprobe "${fs[device]}" || true
done
for swapDev in "@{config.swapDevices!catAttrs.device[@]}" ; do
@ -171,7 +175,7 @@ function format-partitions {( set -eu
elif [[ $swapDev == /dev/mapper/* ]] ; then
if [[ ! @{config.boot.initrd.luks.devices!catAttrSets.device[${swapDev/'/dev/mapper/'/}]:-} ]] ; then echo "LUKS device $swapDev used for SWAP does not point at one of the device mappings @{!config.boot.initrd.luks.devices!catAttrSets.device[@]}" ; exit 1 ; fi
else continue ; fi
( set -x ; mkswap "$swapDev" >$beQuiet )
( set -x ; mkswap "$swapDev" >$beLoud 2>$beSilent )
done
)}

View File

@ -5,6 +5,7 @@
## Entry point to the installation, see »./README.md«.
function install-system {( set -eu # 1: blockDev
trap - EXIT # start with empty traps for sub-shell
prepare-installer "$@"
do-disk-setup "${argv[0]}"
install-system-to $mnt
@ -15,7 +16,7 @@ function prepare-installer { # ...
generic-arg-parse "$@"
beQuiet=/dev/null ; if [[ ${args[debug]:-} ]] ; then set -x ; beQuiet=/dev/stdout ; fi
if [[ ${args[debug]:-} ]] ; then set -x ; fi
: ${argv[0]:?"Required: Target disk or image paths."}
@ -32,9 +33,15 @@ function prepare-installer { # ...
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." ; exit 1 ; fi
done
if [[ ${SUDO_USER:-} ]] ; then function nix {( set +x ; declare -a args=("$@") ; PATH=$hostPath su - "$SUDO_USER" -c "$(declare -p args)"' ; nix "${args[@]}"' )} ; fi
if [[ ${SUDO_USER:-} ]] ; then # use Nix as the user who called this script, as Nix may not be set up for root
function nix {( set +x ; declare -a args=("$@") ; PATH=$hostPath su - "$SUDO_USER" -c "$(declare -p args)"' ; nix "${args[@]}"' )}
else # use Nix by absolute path, as it won't be on »$PATH«
PATH=$PATH:@{native.nix}/bin
fi
if [[ ${args[debug]:-} ]] ; then set +e ; set -E ; trap 'code= ; cat 2>/dev/null || true ; @{native.bashInteractive}/bin/bash --init-file @{config.environment.etc.bashrc.source} || code=$? ; if [[ $code ]] ; then exit $code ; fi' ERR ; fi # On error, instead of exiting straight away, open a shell to allow diagnosing/fixing the issue. Only exit if that shell reports failure (e.g. CtrlC + CtrlD). Unfortunately, the exiting has to be repeated for level of each nested sub-shells. The cat eats anything lined up on stdin, which would otherwise be run in the shell (TODO: but it blocks if there is nothing on stdin, requiring Ctrl+D to be pressed).
_set_x='set -x' ; if [[ ${args[quiet]:-} ]] ; then _set_x=: ; fi
if [[ ${args[debug]:-} ]] ; then set +e ; set -E ; trap 'code= ; timeout .2s cat &>/dev/null || true ; @{native.bashInteractive}/bin/bash --init-file @{config.environment.etc.bashrc.source} || code=$? ; if [[ $code ]] ; then exit $code ; fi' ERR ; fi # On error, instead of exiting straight away, open a shell to allow diagnosing/fixing the issue. Only exit if that shell reports failure (e.g. CtrlC + CtrlD). Unfortunately, the exiting has to be repeated for each level of each nested sub-shells. The »timeout cat« eats anything lined up on stdin, which would otherwise be sent to bash and interpreted as commands.
export PATH=$PATH:@{native.util-linux}/bin # Doing a system installation requires a lot of stuff from »util-linux«. This should probably be moved into the individual functions that actually use the tools ...
@ -48,9 +55,9 @@ function nixos-install-cmd {( set -eu # 1: mnt, 2: topLevel
## 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.
# »$topLevel« may point to an alternative top-level dependency to install.
function install-system-to {( set -eu # 1: mnt, 2?: topLevel
function install-system-to {( set -eu # 1: mnt
mnt=$1 ; topLevel=${2:-}
targetSystem=@{config.system.build.toplevel}
targetSystem=${args[toplevel]:-@{config.system.build.toplevel}}
trap - EXIT # start with empty traps for sub-shell
# Link/create files that some tooling expects:
@ -79,7 +86,7 @@ function install-system-to {( set -eu # 1: mnt, 2?: topLevel
# Copy system closure to new nix store:
if [[ ${SUDO_USER:-} ]] ; then chown -R $SUDO_USER: $mnt/nix/store $mnt/nix/var ; fi
( set -x ; time nix --extra-experimental-features nix-command copy --no-check-sigs --to $mnt ${topLevel:-$targetSystem} ) ; rm -rf $mnt/nix/var/nix/gcroots
( cmd=( nix --extra-experimental-features nix-command --offline copy --no-check-sigs --to $mnt ${topLevel:-$targetSystem} ) ; if [[ ${args[quiet]:-} ]] ; then "${cmd[@]}" ; else set -x ; time "${cmd[@]}" ; fi ) ; rm -rf $mnt/nix/var/nix/gcroots
# TODO: if the target has @{config.nix.autoOptimiseStore} and the host doesn't (there is no .links dir?), optimize now
if [[ ${SUDO_USER:-} ]] ; then chown -R root:root $mnt/nix $mnt/nix/var ; chown :30000 $mnt/nix/store ; fi

View File

@ -35,7 +35,10 @@ function populate-keystore { { # (void)
done
for usage in "${!methods[@]}" ; do
if [[ "${methods[$usage]}" == home-composite || "${methods[$usage]}" == copy ]] ; then continue ; fi
gen-key-"${methods[$usage]}" "$usage" "${options[$usage]}" | write-secret "$keystore"/"$usage".key || return 1
for attempt in 2 3 x ; do
if gen-key-"${methods[$usage]}" "$usage" "${options[$usage]}" | write-secret "$keystore"/"$usage".key ; then break ; fi
if [[ $attempt == x ]] ; then return 1 ; fi ; echo "Retrying ($attempt/3):"
done
done
for usage in "${!methods[@]}" ; do
if [[ "${methods[$usage]}" != home-composite ]] ; then continue ; fi
@ -57,10 +60,10 @@ function create-luks-layers {( set -eu # (void)
primaryKey="$keystore"/luks/"$luksName"/0.key
keyOptions=( --pbkdf=pbkdf2 --pbkdf-force-iterations=1000 )
( PATH=@{native.cryptsetup}/bin ; set -x ; cryptsetup --batch-mode luksFormat --key-file="$primaryKey" "${keyOptions[@]}" -c aes-xts-plain64 -s 512 -h sha256 "$rawDev" )
( PATH=@{native.cryptsetup}/bin ; ${_set_x:-:} ; cryptsetup --batch-mode luksFormat --key-file="$primaryKey" "${keyOptions[@]}" -c aes-xts-plain64 -s 512 -h sha256 "$rawDev" )
for index in 1 2 3 4 5 6 7 ; do
if [[ -e "$keystore"/luks/"$luksName"/"$index".key ]] ; then
( PATH=@{native.cryptsetup}/bin ; set -x ; cryptsetup luksAddKey --key-file="$primaryKey" "${keyOptions[@]}" "$rawDev" "$keystore"/luks/"$luksName"/"$index".key )
( PATH=@{native.cryptsetup}/bin ; ${_set_x:-:} ; cryptsetup luksAddKey --key-file="$primaryKey" "${keyOptions[@]}" "$rawDev" "$keystore"/luks/"$luksName"/"$index".key )
fi
done
done
@ -74,7 +77,7 @@ function open-luks-layers { # (void)
rawDev=@{config.boot.initrd.luks.devices!catAttrSets.device[$luksName]}
primaryKey="$keystore"/luks/"$luksName"/0.key
( PATH=@{native.cryptsetup}/bin ; set -x ; cryptsetup --batch-mode luksOpen --key-file="$primaryKey" "$rawDev" "$luksName" ) &&
( PATH=@{native.cryptsetup}/bin ; ${_set_x:-:} ; cryptsetup --batch-mode luksOpen --key-file="$primaryKey" "$rawDev" "$luksName" ) &&
prepend_trap "@{native.cryptsetup}/bin/cryptsetup close $luksName" EXIT
done
}

View File

@ -74,10 +74,11 @@ function run-qemu {( set -eu # 1: diskImages
done
if [[ @{config.boot.loader.systemd-boot.enable} || ${args[efi]:-} ]] ; then # UEFI. Otherwise it boots something much like a classic BIOS?
#qemu+=( -bios @{pkgs.OVMF.fd}/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).
qemu+=( -drive file=@{pkgs.OVMF.fd}/FV/OVMF_CODE.fd,if=pflash,format=raw,unit=0,readonly=on )
qemu+=( -drive file=/tmp/qemu-@{config.networking.hostName}-VARS.fd,if=pflash,format=raw,unit=1 )
if [[ ! -e /tmp/qemu-@{config.networking.hostName}-VARS.fd ]] ; then cat @{pkgs.OVMF.fd}/FV/OVMF_VARS.fd > /tmp/qemu-@{config.networking.hostName}-VARS.fd ; fi
ovmf=$( @{native.nixVersions.nix_2_9}/bin/nix --extra-experimental-features 'nix-command flakes' build --no-link --print-out-paths @{inputs.nixpkgs}'#'legacyPackages.@{pkgs.system}.OVMF.fd )
#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).
qemu+=( -drive file=${ovmf}/FV/OVMF_CODE.fd,if=pflash,format=raw,unit=0,readonly=on )
qemu+=( -drive file="${args[efi-vars]:-/tmp/qemu-@{config.networking.hostName}-VARS.fd}",if=pflash,format=raw,unit=1 )
if [[ ! -e "${args[efi-vars]:-/tmp/qemu-@{config.networking.hostName}-VARS.fd}" ]] ; then cat ${ovmf}/FV/OVMF_VARS.fd > "${args[efi-vars]:-/tmp/qemu-@{config.networking.hostName}-VARS.fd}" ; fi
# https://lists.gnu.org/archive/html/qemu-discuss/2018-04/msg00045.html
fi
if [[ @{config.wip.preface.hardware} == aarch64 ]] ; then
@ -87,7 +88,7 @@ function run-qemu {( set -eu # 1: diskImages
# Add »config.boot.kernelParams = [ "console=tty1" "console=ttyS0" ]« to log to serial (»ttyS0«) and/or the display (»tty1«), preferring the last »console« option for the initrd shell (if enabled and requested).
logSerial= ; if [[ ' '"@{config.boot.kernelParams[@]}"' ' == *' console=ttyS0'@( |,)* ]] ; then logSerial=1 ; fi
logScreen= ; if [[ ' '"@{config.boot.kernelParams[@]}"' ' == *' console=tty1 '* ]] ; then logScreen=1 ; fi
if [[ $logSerial ]] ; then
if [[ ! ${args[no-serial]:-} && $logSerial ]] ; then
if [[ $logScreen || ${args[graphic]:-} ]] ; then
qemu+=( -serial mon:stdio )
else

View File

@ -19,6 +19,40 @@ function generic-arg-parse { # ...
shift ; done
}
## Shows the help text for a program and exits, if »--help« was passed as argument and parsed, or does nothing otherwise.
# Expects to be called between parsing and verifying the arguments.
# Uses »allowedArgs« for the list of the named arguments (the values are the descriptions).
# »name« should be the program name/path (usually »$0«), »args« the form/names of any positional arguments expected (e.g. »SOURCE... DEST«) and is included in the "Usage" description,
# »description« the introductory text shown before the "Usage", and »suffix« any text printed after the argument list.
function generic-arg-help { # 1: name, 2?: args, 3?: description, 4?: suffix
if [[ ! ${args[help]:-} ]] ; then : ${allowedArgs[help]:=1} ; return 0 ; fi
[[ ! ${3:-} ]] || echo "$3"
printf 'Usage:\n %s [ARG[=value]]... [--] %s\n\nWhere »ARG« may be any of:\n' "$1" "${2:-}"
local name ; while IFS= read -r name ; do
printf ' %s\n %s\n' "$name" "${allowedArgs[$name]}"
done < <( printf '%s\n' "${!allowedArgs[@]}" | LC_ALL=C sort )
printf ' %s\n %s\n' "--help" "Do nothing but print this message and exit with success."
[[ ! ${4:-} ]] || echo "$4"
exit 0
}
## Performs a basic verification of the named arguments passed by the user and parsed by »generic-arg-parse« against the names in »allowedArgs«.
# Entries in »allowedArgs« should have the form »[--name]="description"« for boolean flags, and »[--name=VAL]="description"« for string arguments.
# »description« is used by »generic-arg-help«. Boolean flags may only have the values »1« (as set by »generic-ags-parse« for flags without value) or be empty.
# »VAL« is purely nominal. Any argument passed that is not in »allowedArgs« raises an error.
function generic-arg-verify { # 1: exitCode
local exitCode=${1:-1}
local names=' '"${!allowedArgs[@]}"
for name in "${!args[@]}" ; do
if [[ ${allowedArgs[--$name]:-} ]] ; then
if [[ ${args[$name]} == '' || ${args[$name]} == 1 ]] ; then continue ; fi
echo "Argument »--$name« should be a boolean, but its value is: ${args[$name]}" ; return $exitCode
fi
if [[ $names == *' --'"$name"'='* || $names == *' --'"$name"'[='* ]] ; then continue ; fi
echo "Unexpected argument »--$name«.${allowedArgs[help]:+ Call with »--help« for a list of valid arguments.}" ; return $exitCode
done
}
## Prepends a command to a trap. Especially useful fo define »finally« commands via »prepend_trap '<command>' EXIT«.
# NOTE: When calling this in a sub-shell whose parents already has traps installed, make sure to do »trap - trapName« first. On a new shell, this should be a no-op, but without it, the parent shell's traps will be added to the sub-shell as well (due to strange behavior of »trap -p« (in bash ~5.1.8)).
function prepend_trap { # 1: command, ...: trapNames

View File

@ -20,7 +20,7 @@ function create-zpools { # 1: mnt
if ! is-partition-on-disks "$part" "${blockDevs[@]}" ; then echo "Partition alias $part used by zpool ${pool[name]} does not point at one of the target disks ${blockDevs[@]}" ; exit 1 ; fi
fi
done
( PATH=@{native.zfs}/bin ; set -x ; zpool create "${args[@]}" -R "$mnt" "${pool[name]}" "${vdevs[@]}" )
( PATH=@{native.zfs}/bin ; ${_set_x:-:} ; zpool create "${args[@]}" -R "$mnt" "${pool[name]}" "${vdevs[@]}" )
) && {
prepend_trap "@{native.zfs}/bin/zpool export '$poolName'" EXIT
} ; done &&
@ -62,7 +62,7 @@ function ensure-datasets {( set -eu # 1: mnt, 2?: filterExp
names=$(IFS=, ; echo "${!props[*]}") ; values=$(IFS=$'\n' ; echo "${props[*]}")
if [[ $values != "$($zfs get -o value -H "$names" "${dataset[name]}")" ]] ; then (
declare -a args=( ) ; for name in "${!props[@]}" ; do args+=( "${name}=${props[$name]}" ) ; done
( PATH=@{native.zfs}/bin ; set -x ; zfs set "${args[@]}" "${dataset[name]}" )
( PATH=@{native.zfs}/bin ; ${_set_x:-:} ; zfs set "${args[@]}" "${dataset[name]}" )
) ; fi
if [[ $cryptRoot && $($zfs get -o value -H encryptionroot "${dataset[name]}") != "$cryptRoot" ]] ; then ( # inherit key from parent (which the parent would also already have done if necessary)
@ -72,28 +72,28 @@ function ensure-datasets {( set -eu # 1: mnt, 2?: filterExp
if [[ $($zfs get -o value -H keystatus "${dataset[name]}") != available ]] ; then
$zfs load-key -L file://"$cryptKey" "${dataset[name]}" # will unload with cryptRoot
fi
( PATH=@{native.zfs}/bin ; set -x ; zfs change-key -i "${dataset[name]}" )
( PATH=@{native.zfs}/bin ; ${_set_x:-:} ; zfs change-key -i "${dataset[name]}" )
) ; fi
else ( # create dataset
if [[ ${props[keyformat]:-} == ephemeral ]] ; then
props[encryption]=aes-256-gcm ; props[keyformat]=hex ; props[keylocation]=file:///dev/stdin ; explicitKeylocation=file:///dev/null
declare -a args=( ) ; for name in "${!props[@]}" ; do args+=( -o "${name}=${props[$name]}" ) ; done
</dev/urandom tr -dc 0-9a-f | head -c 64 | ( PATH=@{native.zfs}/bin ; set -x ; zfs create "${args[@]}" "${dataset[name]}" )
</dev/urandom tr -dc 0-9a-f | head -c 64 | ( PATH=@{native.zfs}/bin ; ${_set_x:-:} ; zfs create "${args[@]}" "${dataset[name]}" )
$zfs unload-key "${dataset[name]}"
else
if [[ $cryptRoot && $cryptRoot != ${dataset[name]} && $($zfs get -o value -H keystatus "$cryptRoot") != available ]] ; then
$zfs load-key -L file://"$cryptKey" "$cryptRoot" ; trap "$zfs unload-key $cryptRoot || true" EXIT
fi
declare -a args=( ) ; for name in "${!props[@]}" ; do args+=( -o "${name}=${props[$name]}" ) ; done
( PATH=@{native.zfs}/bin ; set -x ; zfs create "${args[@]}" "${dataset[name]}" )
( PATH=@{native.zfs}/bin ; ${_set_x:-:} ; zfs create "${args[@]}" "${dataset[name]}" )
fi
if [[ ${props[canmount]} != off ]] ; then (
mount -t zfs -o zfsutil "${dataset[name]}" $tmpMnt ; trap "umount '${dataset[name]}'" EXIT
chmod 000 "$tmpMnt" ; ( chown "${dataset[uid]}:${dataset[gid]}" -- "$tmpMnt" ; chmod "${dataset[mode]}" -- "$tmpMnt" )
) ; fi
if [[ $explicitKeylocation && $explicitKeylocation != "${props[keylocation]:-}" ]] ; then
( PATH=@{native.zfs}/bin ; set -x ; zfs set keylocation="$explicitKeylocation" "${dataset[name]}" )
( PATH=@{native.zfs}/bin ; ${_set_x:-:} ; zfs set keylocation="$explicitKeylocation" "${dataset[name]}" )
fi
$zfs snapshot -r "${dataset[name]}"@empty
) ; fi
@ -101,7 +101,7 @@ function ensure-datasets {( set -eu # 1: mnt, 2?: filterExp
eval 'declare -A allows='"${dataset[permissions]}"
for who in "${!allows[@]}" ; do
# »zfs allow $dataset« seems to be the only way to view permissions, and that is not very parsable -.-
( PATH=@{native.zfs}/bin ; set -x ; zfs allow -$who "${allows[$who]}" "${dataset[name]}" >&2 )
( PATH=@{native.zfs}/bin ; ${_set_x:-:} ; zfs allow -$who "${allows[$who]}" "${dataset[name]}" >&2 )
done
done

View File

@ -2,7 +2,7 @@
# NixOS Modules
A NixOS module is a collection of any number of NixOS option definitions and value assignments to those or other options.
While the set of imported modules and thereby defined options is static (in this case starting with the modules passed to `mkNixosSystem` in `../flake.nix`), the value assignments can generally be contingent on other values (as long as there are no logical loops), making for a highly flexible system construction.
While the set of imported modules, and thereby that of the defined options, is static (in this case starting with the modules passed to `mkNixosSystem` in `../flake.nix`), the value assignments can generally be contingent on other values (as long as there are no logical loops), making for a highly flexible system construction.
Since modules can't be imported (or excluded) dynamically, most modules have an `enable` option, which, if false, effectively disables whatever that module does.
Ultimately, the goal of a NixOS configuration is to build an operating system, which is basically a structured collection of program and configuration files.

View File

@ -23,6 +23,7 @@ in {
name = lib.mkOption { description = "Name that this device is being referred to as in other places."; type = lib.types.str; default = name; readOnly = true; };
guid = lib.mkOption { description = "GPT disk GUID of the disk."; type = types.guid; default = lib.wip.sha256guid ("gpt-disk:${name}"+":${globalConfig.networking.hostName}"); };
size = lib.mkOption { description = "The size of the disk, either as number in bytes or as argument to »parseSizeSuffix«. When installing to a physical device, its size must match; images are created with this size."; type = lib.types.either lib.types.ints.unsigned lib.types.str; apply = lib.wip.parseSizeSuffix; default = "16G"; };
sectorSize = lib.mkOption { description = "The disk's logical sector size in bytes. Used to convert (e.g.) partition sizes in sectors to bytes or vice versa."; type = lib.types.ints.unsigned; default = 512; };
allowLarger = lib.mkOption { description = "Whether to allow installation to a physical disk that is larger than the declared size."; type = lib.types.bool; default = true; };
serial = lib.mkOption { description = "Serial number of the specific hardware device to use. If set the device path passed to the installer must point to the device with this serial. Use » udevadm info --query=property --name=$DISK | grep -oP 'ID_SERIAL_SHORT=\K.*' || echo '<none>' « to get the serial."; type = lib.types.nullOr lib.types.str; default = null; };
alignment = lib.mkOption { description = "Default alignment quantifier for partitions on this device. Should be at least the optimal physical write size of the device, but going larger at worst wastes this many times the number of partitions disk sectors."; type = lib.types.int; default = 16384; };
@ -73,7 +74,7 @@ in {
esc = lib.escapeShellArg;
in pkgs.runCommand "partitioning-${config.networking.hostName}" { } ''
${lib.wip.substituteImplicit { inherit pkgs; scripts = [ partition-disk ]; context = { inherit config; native = pkgs; }; }} # inherit (builtins) trace;
mkdir $out ; beQuiet=/dev/stdout
mkdir $out ; declare -A args=([debug]=1)
${lib.concatStrings (lib.mapAttrsToList (name: disk: ''
name=${esc name} ; img=$name.img
${pkgs.coreutils}/bin/truncate -s ${esc disk.size} "$img"

View File

@ -31,7 +31,8 @@ in {
owner = "sbabic"; repo = pname; rev = "ba7564f5006d09bec51058cf4f5ac90d4dc18b3c"; # 2018-11-18
hash = "sha256-6cHkr3s7/2BVXBTn9bUfPFbYAfv9VYh6C9GAbWILNjs=";
};
nativeBuildInputs = [ pkgs.cmake pkgs.zlib ];
nativeBuildInputs = [ pkgs.cmake ];
buildInputs = [ pkgs.cmake pkgs.zlib ];
outputs = [ "out" "lib" ];
meta = {

View File

@ -52,4 +52,8 @@ in {
printf "%s\n%s\n" "#define CONFIG_EXTRA_ENV_SETTINGS $CONFIG_EXTRA_ENV_SETTINGS" "$(cat include/env_default.h)" >include/env_default.h
'';
});
ubootTools = prev.ubootTools.overrideAttrs (old: {
buildInputs = (old.buildInputs or [ ]) ++ [ final.openssl ];
});
}