diff --git a/lib/flakes.nix b/lib/flakes.nix index fd24d24..ccfe13f 100644 --- a/lib/flakes.nix +++ b/lib/flakes.nix @@ -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; + })); } diff --git a/lib/setup-scripts/add-key.sh b/lib/setup-scripts/add-key.sh index c7160e5..a66bd11 100644 --- a/lib/setup-scripts/add-key.sh +++ b/lib/setup-scripts/add-key.sh @@ -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 diff --git a/lib/setup-scripts/disk.sh b/lib/setup-scripts/disk.sh index e3dd426..80cd0cc 100644 --- a/lib/setup-scripts/disk.sh +++ b/lib/setup-scripts/disk.sh @@ -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 )} diff --git a/lib/setup-scripts/install.sh b/lib/setup-scripts/install.sh index 577ff0a..ed8764a 100644 --- a/lib/setup-scripts/install.sh +++ b/lib/setup-scripts/install.sh @@ -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 diff --git a/lib/setup-scripts/keys.sh b/lib/setup-scripts/keys.sh index 69c8efb..c9d9e8c 100644 --- a/lib/setup-scripts/keys.sh +++ b/lib/setup-scripts/keys.sh @@ -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 } diff --git a/lib/setup-scripts/maintenance.sh b/lib/setup-scripts/maintenance.sh index 2544bbb..09f8e75 100644 --- a/lib/setup-scripts/maintenance.sh +++ b/lib/setup-scripts/maintenance.sh @@ -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 diff --git a/lib/setup-scripts/utils.sh b/lib/setup-scripts/utils.sh index a1d802b..abef079 100644 --- a/lib/setup-scripts/utils.sh +++ b/lib/setup-scripts/utils.sh @@ -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 '' 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 diff --git a/lib/setup-scripts/zfs.sh b/lib/setup-scripts/zfs.sh index 0e78271..6a93727 100644 --- a/lib/setup-scripts/zfs.sh +++ b/lib/setup-scripts/zfs.sh @@ -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 - include/env_default.h ''; }); + + ubootTools = prev.ubootTools.overrideAttrs (old: { + buildInputs = (old.buildInputs or [ ]) ++ [ final.openssl ]; + }); }