mirror of
https://github.com/NiklasGollenstede/nixos-installer.git
synced 2024-11-21 23:43:14 +01:00
better CLI argument handling, some small fixes
This commit is contained in:
parent
7ab9215b0a
commit
1d93a8acc0
@ -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;
|
||||
}));
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
)}
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
@ -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.
|
||||
|
@ -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"
|
||||
|
@ -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 = {
|
||||
|
@ -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 ];
|
||||
});
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user