make partitioning reproducible, add sgdisk patch,

do partitioning during build instead of install,
fix qemu's efi vars,
add config.wip.dropbear.hostKeys
This commit is contained in:
Niklas Gollenstede 2022-06-13 21:16:20 +02:00
parent e46b671f8f
commit b51cf19f4a
14 changed files with 322 additions and 47 deletions

11
.vscode/settings.json vendored
View File

@ -34,6 +34,7 @@
"dedup", // zfs
"deps", // abbr dependencies
"devs", // abbr (devices)
"dmask", // mount
"dnodesize", // zfs
"dontUnpack", // nixos
"dosfstools", // package
@ -49,11 +50,13 @@
"fetchurl", // nix function
"filesystems", // plural
"findutils", // package
"fmask", // mount
"foldl", // nix (fold left)
"foldr", // nix (fold right)
"FUNCNAME", // bash var
"fw_printenv", // program
"fw_setenv", // program
"gcroots", // Nix
"gdisk", // program
"getsize64", // cli arg
"getty", // serice
@ -64,6 +67,7 @@
"gptfdisk", // package
"headlessly", // word
"hostbus", // cli arg
"hostfwd", // cli arg
"hostiocache", // virtual box
"hostport", // cli arg
"inetutils", // package
@ -74,6 +78,7 @@
"keyformat", // zfs
"keylocation", // zfs
"keystatus", // zfs
"kmod", // linux
"lazytime", // f2fs
"libubootenv", // package
"logbias", // zfs
@ -86,6 +91,7 @@
"mktemp", // program / function
"modifyvm", // virtual box
"mountpoint", // program / function
"mtab", // linux
"namespacing", // word
"netbootxyz", // option
"netdev", // cli arg
@ -100,6 +106,7 @@
"noexec", // mount option
"nofail", // cli arg
"nographic", // cli arg
"noheadings", // cli arg
"nosuid", // mount option
"oneshot", // systemd
"ostype", // virtual box
@ -107,6 +114,7 @@
"partlabel", // linux
"partprobe", // program / function
"pbkdf", // cli arg
"pflash", // cli arg
"pkgs", // nix
"pname", // nix/abbr (package name)
"portcount", // virtual box
@ -136,6 +144,7 @@
"stdenv", // nix
"storageattach", // virtual box
"stty", // program / function
"sublist", // Nix
"syncoid", // program
"temproot", // abbr (temporary root (FS))
"timesync", // systemd
@ -149,9 +158,11 @@
"udev", // program
"udevadm", // program
"udptunnel", // program
"UEFI", // abbr
"uids", // abbr/plural (group IDs)
"unencrypted", // ~= not encrypted / decrypted
"upperdir", // mount overlay option
"upstreamed", // word
"urandom", // linux
"vboxusers", // virtual box
"vdev", "vdevs", // zfs

View File

@ -20,7 +20,7 @@
}; in (import "${./.}/lib/flakes.nix" "${./.}/lib" inputs).patchFlakeInputsAndImportRepo inputs patches ./. (inputs@ { self, nixpkgs, ... }: repo@{ overlays, lib, ... }: let
systemsFlake = lib.wip.mkSystemsFlake (rec {
#systems = { dir = "${inputs.self.outPath}/hosts"; exclude = [ ]; };
#systems = { dir = "${inputs.self}/hosts"; exclude = [ ]; }; # (implicit)
inherit inputs;
scripts = (lib.attrValues lib.wip.setup-scripts) ++ [ ./example/install.sh.md ];
});
@ -32,5 +32,5 @@ in [ # Run »nix flake show --allow-import-from-derivation« to see what this me
packages = lib.wip.getModifiedPackages (lib.wip.importPkgs inputs { system = localSystem; }) overlays;
defaultPackage = systemsFlake.packages.${localSystem}.all-systems;
}))
{ patches = import "${inputs.self.outPath}/patches" "${inputs.self.outPath}/patches" inputs; }
{ patches = import "${inputs.self}/patches" "${inputs.self}/patches" inputs; }
]); }

View File

@ -121,5 +121,7 @@ in { imports = [ ({ ## Hardware
wip.services.dropbear.enable = true;
#wip.services.dropbear.rootKeys = [ ''${lib.readFile "${dirname}/....pub"}'' ];
#wip.fs.disks.devices.primary.gptOffset = 64;
#wip.fs.disks.devices.primary.size = "250059096K"; # 256GB Intel H10
}) ]; }

View File

@ -22,9 +22,15 @@ in rec {
# Lists will be declared as bash arrays, attribute sets will be declared as associative arrays using »asBashDict«.
# Bash does not support any nested data structures. Lists or attrsets in within lists or attrsets are therefore (recursively) encoded and escaped as strings, such that calling »eval« on them is safe if (but only if) they are known to be encoded from nested lists/attrsets. Example: »eval 'declare -A fs='"@{config.fileSystems['/']}" ; root=${fs[device]}«.
# Any other value (functions), and things that »builtins.toString« doesn't like, will throw here.
substituteImplicit = args@{ pkgs, scripts, context, helpers ? { }, trace ? (m: v: v), }: let
substituteImplicit = args@{
scripts, # List of paths to scripts to process and then source in the returned script. Can also be an attrset, of which (only) the values will be used, and each script may also be an attrset »{ name; text; }« instead of a path.
context, # The root attrset for the resolution of substitutions.
pkgs, # Instantiated »nixpkgs«, as fallback location for helpers, and to grab »writeScript« etc from.
helpers ? { }, # Attrset of (highest priority) helper functions.
trace ? (m: v: v), # Function that gets called with the names and values as they are processed. Pass »builtins.trace« for debugging, esp. when evaluating one of the accessed values fails.
}: let
scripts = map (source: rec {
text = builtins.readFile source; inherit source;
text = if builtins.isAttrs source then source.text else builtins.readFile source; name = if builtins.isAttrs source then source.name else builtins.baseNameOf source;
parsed = builtins.split ''@\{([#!]?)([a-zA-Z][a-zA-Z0-9_.-]*[a-zA-Z0-9](![a-zA-Z][a-zA-Z0-9_.-]*[a-zA-Z0-9])?)([:*@\[#%/^,\}])'' text; # (first part of a bash parameter expansion, with »@« instead of »$«)
}) (if builtins.isAttrs args.scripts then builtins.attrValues args.scripts else args.scripts);
decls = lib.unique (map (match: builtins.elemAt match 1) (builtins.filter builtins.isList (builtins.concatMap (script: script.parsed) scripts)));
@ -56,7 +62,7 @@ in rec {
); in trace final final)) decls));
in ''
source ${vars}
${lib.concatMapStringsSep "\n" (script: "source ${pkgs.writeScript (builtins.baseNameOf script.source) (
${lib.concatMapStringsSep "\n" (script: "source ${pkgs.writeScript script.name (
lib.concatMapStringsSep "" (seg: if builtins.isString seg then seg else (
"$"+"{"+(builtins.head seg)+(builtins.replaceStrings [ "." "!" "-" ] [ "_" "1" "0" ] (builtins.elemAt seg 1))+(toString (builtins.elemAt seg 3))
)) script.parsed

View File

@ -46,13 +46,15 @@ function partition-disks { { # 1: diskPaths
local name ; for name in "@{!config.wip.fs.disks.devices[@]}" ; do
if [[ ! ${blockDevs[$name]:-} ]] ; then echo "Path for block device $name not provided" ; exit 1 ; fi
eval 'local -A disk='"@{config.wip.fs.disks.devices[$name]}"
if [[ ${blockDevs[$name]} != /dev/* ]] ; then
local outFile=${blockDevs[$name]} ; ( set -eu
eval 'declare -A disk='"@{config.wip.fs.disks.devices[$name]}"
install -o root -g root -m 640 -T /dev/null "$outFile" && truncate -s "${disk[size]}" "$outFile"
) && blockDevs[$name]=$(losetup --show -f "$outFile") && prepend_trap "losetup -d '${blockDevs[$name]}'" EXIT # NOTE: this must not be inside a sub-shell!
local outFile=${blockDevs[$name]} &&
install -o root -g root -m 640 -T /dev/null "$outFile" && truncate -s "${disk[size]}" "$outFile" &&
blockDevs[$name]=$(losetup --show -f "$outFile") && prepend_trap "losetup -d '${blockDevs[$name]}'" EXIT # NOTE: this must not be inside a sub-shell!
else
if [[ ! "$(blockdev --getsize64 "${blockDevs[$name]}")" ]] ; then echo "Block device $name does not exist at ${blockDevs[$name]}" ; exit 1 ; fi
local size=$( blockdev --getsize64 "${blockDevs[$name]}" || : )
if [[ ! $size ]] ; then echo "Block device $name does not exist at ${blockDevs[$name]}" ; exit 1 ; fi
if [[ $size != "${disk[size]}" ]] ; then echo "Block device ${blockDevs[$name]}'s size $size does not match the size ${disk[size]} declared for $name" ; exit 1 ; fi
blockDevs[$name]=$(realpath "${blockDevs[$name]}")
fi
done
@ -67,10 +69,12 @@ function partition-disks { { # 1: diskPaths
for name in "@{!config.wip.fs.disks.devices[@]}" ; do
eval 'declare -A disk='"@{config.wip.fs.disks.devices[$name]}"
if [[ ${disk[serial]:-} ]] ; then
actual=$(udevadm info --query=property --name="$blockDev" | grep -oP 'ID_SERIAL_SHORT=\K.*')
if [[ ${disk[serial]} != "$actual" ]] ; then echo "Block device $blockDev does not match the serial declared for ${disk[name]}" ; exit 1 ; fi
actual=$( udevadm info --query=property --name="$blockDev" | grep -oP 'ID_SERIAL_SHORT=\K.*' || echo '<none>' )
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
partition-disk "${disk[name]}" "${blockDevs[${disk[name]}]}"
# can (and probably should) restore the backup:
( PATH=@{native.gptfdisk}/bin ; set -x ; sgdisk --load-backup=@{config.wip.fs.disks.partitioning}/"${disk[name]}".backup "${blockDevs[${disk[name]}]}" >$beQuiet )
#partition-disk "${disk[name]}" "${blockDevs[${disk[name]}]}"
done
@{native.parted}/bin/partprobe "${blockDevs[@]}"
@{native.systemd}/bin/udevadm settle -t 15 || true # sometimes partitions aren't quite made available yet
@ -83,16 +87,28 @@ function partition-disks { { # 1: diskPaths
function partition-disk {( set -eu # 1: name, 2: blockDev, 3?: devSize
name=$1 ; blockDev=$2
eval 'declare -A disk='"@{config.wip.fs.disks.devices[$name]}"
devSize=${3:-$(@{native.util-linux}/bin/blockdev --getsize64 "$blockDev")}
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-secondary-table=$(( devSize/512 - 1 - 32 - ${disk[gptOffset]} )) )
fi
sgdisk+=( --disk-guid="${disk[guid]}" )
for partDecl in "@{config.wip.fs.disks.partitionList[@]}" ; do
eval 'declare -A part='"$partDecl"
if [[ ${part[disk]} != "${disk[name]}" ]] ; then continue ; fi
if [[ ${part[size]:-} =~ ^[0-9]+%$ ]] ; then
part[size]=$(( $devSize / 1024 * ${part[size]:0:(-1)} / 100 ))K
fi
sgdisk+=( -a "${part[alignment]:-${disk[alignment]}}" -n "${part[index]:-0}":"${part[position]}":+"${part[size]:-}" -t 0:"${part[type]}" -c 0:"${part[name]}" )
sgdisk+=(
--set-alignment="${part[alignment]:-${disk[alignment]}}"
--new="${part[index]:-0}":"${part[position]}":+"${part[size]:-}"
--partition-guid=0:"${part[guid]}"
--typecode=0:"${part[type]}"
--change-name=0:"${part[name]}"
)
done
if [[ ${disk[mbrParts]:-} ]] ; then

View File

@ -67,8 +67,12 @@ function run-qemu {( set -eu # 1: diskImages
qemu+=( -drive format=raw,file="${decl/*=/}" ) #,if=none,index=0,media=disk,id=disk0 -device "virtio-blk-pci,drive=disk0,disable-modern=on,disable-legacy=off" )
done
if [[ @{config.boot.loader.systemd-boot.enable} || ${args[efi]:-} ]] ; then
qemu+=( -bios @{pkgs.OVMF.fd}/FV/OVMF.fd ) # UEFI. Otherwise it boots something much like a classic BIOS?
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
# https://lists.gnu.org/archive/html/qemu-discuss/2018-04/msg00045.html
fi
if [[ @{config.preface.hardware} == aarch64 ]] ; then
qemu+=( -kernel @{config.system.build.kernel}/Image -initrd @{config.system.build.initialRamdisk}/initrd -append "$(echo -n "@{config.boot.kernelParams[@]}")" )
@ -79,7 +83,7 @@ function run-qemu {( set -eu # 1: diskImages
fi ; done
if [[ ! ${args[no-nat]:-} ]] ; then
qemu+=( -nic user,model=virtio-net-pci ) # NATed, IPs: 10.0.2.15+/32, gateway: 10.0.2.2
qemu+=( -nic user,model=virtio-net-pci${args[nat-fw]:+,hostfwd=tcp::${args[nat-fw]//,/,hostfwd=tcp::}} ) # NATed, IPs: 10.0.2.15+/32, gateway: 10.0.2.2
fi
# TODO: network bridging:
@ -91,7 +95,11 @@ function run-qemu {( set -eu # 1: diskImages
qemu+=( -usb -device usb-host,hostbus="${decl/-*/}",hostport="${decl/*-/}" )
done ; fi
if [[ ${args[dry-run]:-} ]] ; then
( echo "${qemu[@]}" )
else
( set -x ; "${qemu[@]}" )
fi
# https://askubuntu.com/questions/54814/how-can-i-ctrl-alt-f-to-get-to-a-tty-in-a-qemu-session
@ -107,7 +115,6 @@ function add-bootkey-to-keydev {( set -eu # 1: blockDev, 2?: hostHash
</dev/urandom tr -dc 0-9a-f | head -c 512 >/dev/disk/by-partlabel/"$bootkeyPartlabel"
)}
## Tries to open and mount the systems keystore from its LUKS partition. If successful, adds the traps to close it when the parent shell exits.
# See »open-system«'s implementation for some example calls to this function.
function mount-keystore-luks { # ...: cryptsetupOptions
@ -141,7 +148,7 @@ function open-system { # 1?: diskImages
( @{native.systemd}/bin/udevadm settle -t 15 || true ) && # sometimes partitions aren't quite made available yet
if [[ @{config.wip.fs.keystore.enable} && ! -e /dev/mapper/keystore-@{config.networking.hostName!hashString.sha256:0:8} ]] ; then # Try a bunch of approaches for opening the keystore:
mount-keystore-luks --key-file=<(printf %s "@{config.networking.hostName}") ||
mount-keystore-luks --key-file=<( printf %s "@{config.networking.hostName}" ) ||
mount-keystore-luks --key-file=/dev/disk/by-partlabel/bootkey-@{config.networking.hostName!hashString.sha256:0:8} ||
mount-keystore-luks --key-file=<( read -s -p PIN: pin && echo ' touch!' >&2 && ykchalresp -2 "$pin" ) ||
# TODO: try static yubikey challenge

View File

@ -88,6 +88,13 @@ in rec {
removeTailingNewline = string: if lastChar string == "\n" then builtins.substring 0 (builtins.stringLength string - 1) string else string;
## Reproducibly generates a GUID by sha256-hashing a prefixed name. The result looks like a RFC 4122 GUID "generated by [SHA1] hashing a namespace identifier and name".
# E.g.: sha256guid "gpt-disk:primary:${hostname}" => "xxxxxxxx-xxxx-5xxx-8xxx-xxxxxxxxxxxx"
sha256guid = name: let
hash = builtins.hashString "sha256" "nixos-guid:${name}";
s = from: to: builtins.substring from (to - from) hash;
in "${s 0 8}-${s 8 12}-5${s 13 16}-8${s 17 20}-${s 20 32}";
## Math

View File

@ -12,6 +12,7 @@ Options to declare devices and partitions to be picked up by the installer scrip
dirname: inputs: { config, pkgs, lib, ... }: let inherit (inputs.self) lib; in let
prefix = inputs.config.prefix;
cfg = config.${prefix}.fs.disks;
types.guid = lib.types.strMatching ''^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$'';
in {
options.${prefix} = { fs.disks = {
@ -19,12 +20,14 @@ in {
description = "Set of disk devices that this host will be installed on.";
type = lib.types.attrsOf (lib.types.nullOr (lib.types.submodule ({ name, ... }: { options = {
name = lib.mkOption { description = "Name that this device is being referred to as in other places."; type = lib.types.str; default = name; readOnly = true; };
size = lib.mkOption { description = "The size of the image to create, when using an image for this device, as argument to »truncate -s«."; type = lib.types.str; default = "16G"; };
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.*'« to get the serial."; type = lib.types.nullOr lib.types.str; default = null; };
guid = lib.mkOption { description = "GPT disk GUID of the disk."; type = types.guid; default = lib.wip.sha256guid ("gpt-disk:${name}"+":${config.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"; };
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; };
gptOffset = lib.mkOption { description = "Offset of the partition tables, inwards from where (third / 2nd last) they usually are."; type = lib.types.ints.unsigned; default = 0; };
mbrParts = lib.mkOption { description = "Up to three colon-separated (GPT) partition numbers that will be made available in a hybrid MBR."; type = lib.types.nullOr lib.types.str; default = null; };
extraFDiskCommands = lib.mkOption { description = "»fdisk« menu commands to run against the hybrid MBR. ».mbrParts« 1[2[3]] exist as transfers from the GPT table, and part4 is the protective GPT part. Can do things like marking partitions as bootable or changing their type. Spaces and end-of-line »#«-prefixed comments are removed, new lines and »;« also mean return."; type = lib.types.lines; default = ""; example = ''
t;1;b # type ; part1 ; W95 FAT32
t;1;c # type ; part1 ; W95 FAT32 (LBA)
a;1 # active/boot ; part1
''; };
}; })));
@ -35,6 +38,7 @@ in {
description = "Set of disks disk partitions that the system will need/use. Partitions will be created on their respective ».disk«s in ».order« using »sgdisk -n X:+0+$size«.";
type = lib.types.attrsOf (lib.types.nullOr (lib.types.submodule ({ name, ... }: { options = {
name = lib.mkOption { description = "Name/partlabel that this partition can be referred to as once created."; type = lib.types.str; default = name; readOnly = true; };
guid = lib.mkOption { description = "GPT partition GUID of the partition."; type = types.guid; default = lib.wip.sha256guid ("gpt-part:${name}"+":${config.networking.hostName}"); };
disk = lib.mkOption { description = "Name of the disk that this partition resides on, which will automatically be declared with default options."; type = lib.types.str; default = "primary"; };
type = lib.mkOption { description = "»gdisk« partition type of this partition."; type = lib.types.str; };
size = lib.mkOption { description = "Partition size, either as integer suffixed with »K«, »M«, »G«, etc for sizes in XiB, or an integer suffixed with »%« for that portion of the size of the actual disk the partition gets created on. Or »null« to fill the remaining disk space."; type = lib.types.nullOr lib.types.str; default = null; };
@ -63,19 +67,19 @@ in {
fs.disks.devices = lib.wip.mapMerge (name: { ${name} = { }; }) (lib.catAttrs "disk" config.${prefix}.fs.disks.partitionList);
fs.disks.partitioning = let
partition-disk = builtins.toFile "partition-disk" (lib.wip.extractBashFunction (builtins.readFile "${dirname}/../../lib/setup-scripts/disk.sh") "partition-disk");
partition-disk = { name = "partition-disk"; text = lib.wip.extractBashFunction (builtins.readFile lib.wip.setup-scripts.disk) "partition-disk"; };
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
${lib.concatStrings (lib.mapAttrsToList (name: disk: ''
name=${esc name} ; img=$name.img
${pkgs.coreutils}/bin/truncate -s ${esc disk.size} $img
partition-disk $name $img ${toString (lib.wip.parseSizeSuffix disk.size)}
${pkgs.gptfdisk}/bin/sgdisk --backup=$out/$name.backup $img
${pkgs.gptfdisk}/bin/sgdisk --print $img >$out/$name.gpt
${pkgs.coreutils}/bin/truncate -s ${esc disk.size} "$img"
partition-disk "$name" "$img" ${toString (lib.wip.parseSizeSuffix disk.size)}
${pkgs.gptfdisk}/bin/sgdisk --backup=$out/"$name".backup "$img"
${pkgs.gptfdisk}/bin/sgdisk --print "$img" >$out/"$name".gpt
${if disk.mbrParts != null then ''
${pkgs.util-linux}/bin/fdisk --type mbr --list $img >$out/$name.mbr
${pkgs.util-linux}/bin/fdisk --type mbr --list "$img" >$out/"$name".mbr
'' else ""}
'') cfg.devices)}
'';

View File

@ -15,7 +15,7 @@ What keys are used for is derived from the attribute name in the `.keys` specifi
* Keys in `home/` are used as composites for home directory encryption, where the second and only other path label us the user name. TODO: this is not completely implemented yet.
The attribute value in the `.keys` keys specification dictates how the key is acquired, primarily initially during installation, but (depending on the keys usage) also during boot unlocking, etc.
The format of the key specification is `method[=args]`, where `method` is the suffix of a bash function call `add-key-<method>` (the default functions are in [`add-key.sh`](../../lib/setup-scripts/add-key.sh), but others could be added to the installer), and `args` is the second argument to the respective function (often a `:` separated list of arguments, but some methods don't need any arguments at all).
The format of the key specification is `method[=args]`, where `method` is the suffix of a bash function call `gen-key-<method>` (the default functions are in [`add-key.sh`](../../lib/setup-scripts/add-key.sh), but others could be added to the installer), and `args` is the second argument to the respective function (often a `:` separated list of arguments, but some methods don't need any arguments at all).
Most key generation methods only make sense in some key usage contexts. A `random` key is impossible to provide to unlock the keystore (which it is stored in), but is well suited to unlock other devices (if the keystore has backups (TODO!)); conversely a USB-partition can be used to headlessly unlock the keystore, but would be redundant for any further devices, as it would also be copied in the keystore.
If the module is `enable`d, a partition and LUKS device `keystore-...` gets configured and the contents of the installation time keystore is copied to it (in its entirety, including intermediate or derived keys and those unlocking the keystore itself (TODO: this could be optimized)).

View File

@ -108,7 +108,7 @@ dirname: inputs: specialArgs@{ config, pkgs, lib, ... }: let inherit (inputs.sel
mounts = lib.mkOption {
description = "Locations (for »temp« in addition to »/«) where a ${desc} filesystem should be mounted. Some are declared by default but may be removed by setting them to »null«.";
type = lib.types.attrsOf (lib.types.nullOr (lib.types.submodule ({ name, ... }: { options = {
target = lib.mkOption { description = "Attribute name as the mount target path."; type = lib.types.addCheck lib.types.str (name: (builtins.match ''^/.*[^/]$'' name) != null); default = name; readOnly = true; };
target = lib.mkOption { description = "Attribute name as the mount target path."; type = lib.types.strMatching ''^/.*[^/]$''; default = name; readOnly = true; };
source = lib.mkOption { description = "Relative source path of the mount. (Irrelevant for »tmpfs«.)"; type = lib.types.str; default = builtins.substring 1 (builtins.stringLength name - 1) name; };
uid = lib.mkOption { description = "UID owning the mounted target."; type = lib.types.ints.unsigned; default = 0; };
gid = lib.mkOption { description = "GID owning the mounted target."; type = lib.types.ints.unsigned; default = 0; };

View File

@ -120,7 +120,7 @@ in let module = {
fi )'';
in {
boot.initrd.postDeviceCommands = lib.mkIf (anyPool "autoApplyDuringBoot") (lib.mkAfter ''
boot.initrd.postDeviceCommands = lib.mkIf (anyPool "autoApplyDuringBoot") (lib.mkOrder 2000 ''
${ensure-datasets-for "autoApplyDuringBoot" "${extraUtils}/bin/zfs"}
'');
boot.initrd.supportedFilesystems = lib.mkIf (anyPool "autoApplyDuringBoot") [ "zfs" ];

View File

@ -17,28 +17,33 @@ in {
options.${prefix} = { services.dropbear = {
enable = lib.mkEnableOption "dropbear SSH daemon";
socketActivation = lib.mkEnableOption "socket activation mode for dropbear";
rootKeys = lib.mkOption { default = [ ]; type = lib.types.listOf lib.types.str; description = "Literal lines to write to »/root/.ssh/authorized_keys«"; };
rootKeys = lib.mkOption { description = "Literal lines to write to »/root/.ssh/authorized_keys«"; default = [ ]; type = lib.types.listOf lib.types.str; };
hostKeys = lib.mkOption { description = "Location of the host key(s) to use. If empty, then a key(s) will be generated at »/etc/dropbear/dropbear_(ecdsa/rsa)_host_key« on first access to the server."; default = [ ]; type = lib.types.listOf lib.types.path; };
}; };
config = lib.mkIf cfg.enable (lib.mkMerge [ ({
config = let
defaultArgs = lib.concatStringsSep "" [
"${pkgs.dropbear}/bin/dropbear"
" -p 22" # listen on TCP/22
(if cfg.hostKeys == [ ] then (
" -R" # generate host keys on connection
) else lib.concatMapStrings (path: (
" -r ${path}"
)) cfg.hostKeys)
];
in lib.mkIf cfg.enable (lib.mkMerge [ ({
environment.systemPackages = (with pkgs; [ dropbear ]);
networking.firewall.allowedTCPPorts = [ 22 ];
#environment.etc."dropbear/.mkdir".text = "";
environment.etc.dropbear.source = "/run/user/0"; # allow for readonly /etc
}) (lib.mkIf (!cfg.socketActivation) {
systemd.services."dropbear" = {
description = "dropbear SSH server (listening)";
wantedBy = [ "multi-user.target" ]; after = [ "network.target" ];
serviceConfig.ExecStart = lib.concatStringsSep "" [
"${pkgs.dropbear}/bin/dropbear"
" -F -E" # don't fork, use stderr
" -p 22" # listen on TCP/22
" -R" # generate host keys on connection
#" -r .../dropbear_rsa_host_key"
];
serviceConfig.PreExec = lib.mkIf (cfg.hostKeys == [ ]) "${pkgs.coreutils}/bin/mkdir -p /etc/dropbear/";
serviceConfig.ExecStart = defaultArgs + " -F -E"; # don't fork, use stderr
#serviceConfig.PIDFile = "/var/run/dropbear.pid"; serviceConfig.Type = "forking"; after = [ "network.target" ]; # alternative to »-E -F« (?)
};
@ -55,12 +60,11 @@ in {
systemd.services."dropbear@" = {
description = "dropbear SSH server (per-connection)";
after = [ "syslog.target" ];
serviceConfig.PreExec = lib.mkIf (cfg.hostKeys == [ ]) "${pkgs.coreutils}/bin/mkdir -p /etc/dropbear/"; # or before socket?
serviceConfig.ExecStart = lib.concatStringsSep "" [
"-" # for the most part ignore exit != 0
"${pkgs.dropbear}/bin/dropbear"
defaultArgs
" -i" # handle a single connection on stdio
" -R" # generate host keys on connection
#" -r .../dropbear_rsa_host_key"
];
};

27
overlays/gptfdisk.nix.md Normal file
View File

@ -0,0 +1,27 @@
/*
# sGdisk with Patches
GPT-FDisk patched to be able to move not only the primary, but also the backup partition table.
## Implementation
```nix
#*/# end of MarkDown, beginning of NixPkgs overlay:
dirname: inputs: final: prev: let
inherit (final) pkgs; inherit (inputs.self) lib;
in {
gptfdisk = prev.gptfdisk.overrideAttrs (old: rec {
pname = "gptfdisk";
version = "1.0.9";
src = builtins.fetchurl {
url = "https://downloads.sourceforge.net/gptfdisk/${pname}-${version}.tar.gz";
sha256 = "1hjh5m77fmfq5m44yy61kchv7mbfgx026aw3jy5qxszsjckavzns";
};
patches = [ # (don't include »old.patches«, as the only one was upstreamed)
../patches/gptfdisk-move-secondary-table.patch
];
});
}

View File

@ -0,0 +1,191 @@
diff --git a/gpt.cc b/gpt.cc
index 76cd9ad..375c216 100644
--- a/gpt.cc
+++ b/gpt.cc
@@ -1500,7 +1500,7 @@ int GPTData::DestroyGPT(void) {
cerr << "Warning! GPT main partition table not overwritten! Error is " << errno << "\n";
allOK = 0;
} // if write failed
- } // if
+ } // if
if (!myDisk.Seek(secondHeader.partitionEntriesLBA))
allOK = 0;
if (allOK) {
@@ -1911,6 +1911,23 @@ int GPTData::MoveMainTable(uint64_t pteSector) {
return retval;
} // GPTData::MoveMainTable()
+// Change the start sector for the secondary partition table.
+// Returns 1 on success, 0 on failure
+int GPTData::MoveSecondaryTable(uint64_t pteSector) {
+ uint64_t pteSize = GetTableSizeInSectors();
+ int retval = 1;
+
+ if ((pteSector > FindLastUsedLBA()) && ((pteSector + pteSize) < diskSize)) {
+ secondHeader.partitionEntriesLBA = pteSector; // (RebuildSecondHeader actually replaces this with lastUsableLBA+1)
+ mainHeader.lastUsableLBA = secondHeader.partitionEntriesLBA - UINT64_C(1);
+ RebuildSecondHeader();
+ } else {
+ cerr << "Unable to set the secondary partition table's location to " << pteSector << "!\n";
+ retval = 0;
+ } // if/else
+ return retval;
+} // GPTData::MoveSecondaryTable()
+
// Blank the partition array
void GPTData::BlankPartitions(void) {
uint32_t i;
@@ -2285,7 +2302,7 @@ uint64_t GPTData::FindFirstAvailable(uint64_t start) {
} // GPTData::FindFirstAvailable()
// Returns the LBA of the start of the first partition on the disk (by
-// sector number), or 0 if there are no partitions defined.
+// sector number), or UINT64_MAX if there are no partitions defined.
uint64_t GPTData::FindFirstUsedLBA(void) {
uint32_t i;
uint64_t firstFound = UINT64_MAX;
@@ -2298,6 +2315,20 @@ uint64_t GPTData::FindFirstUsedLBA(void) {
return firstFound;
} // GPTData::FindFirstUsedLBA()
+// Returns the LBA of the end of the last partition on the disk (by
+// sector number), or 0 if there are no partitions defined.
+uint64_t GPTData::FindLastUsedLBA(void) {
+ uint32_t i;
+ uint64_t lastFound = 0;
+
+ for (i = 0; i < numParts; i++) {
+ if ((partitions[i].IsUsed()) && (partitions[i].GetFirstLBA() > lastFound)) {
+ lastFound = partitions[i].GetFirstLBA();
+ } // if
+ } // for
+ return lastFound;
+} // GPTData::FindLastUsedLBA()
+
// Finds the first available sector in the largest block of unallocated
// space on the disk. Returns 0 if there are no available blocks left
uint64_t GPTData::FindFirstInLargest(void) {
diff --git a/gpt.h b/gpt.h
index 5d19372..c272d65 100644
--- a/gpt.h
+++ b/gpt.h
@@ -142,6 +142,7 @@ public:
// Adjust GPT structures WITHOUT user interaction...
int SetGPTSize(uint32_t numEntries, int fillGPTSectors = 1);
int MoveMainTable(uint64_t pteSector);
+ int MoveSecondaryTable(uint64_t pteSector);
void BlankPartitions(void);
int DeletePartition(uint32_t partNum);
uint32_t CreatePartition(uint32_t partNum, uint64_t startSector, uint64_t endSector);
@@ -158,7 +159,7 @@ public:
void RecomputeCHS(void);
int Align(uint64_t* sector);
void SetProtectiveMBR(BasicMBRData & newMBR) {protectiveMBR = newMBR;}
-
+
// Return data about the GPT structures....
WhichToUse GetState(void) {return whichWasUsed;}
int GetPartRange(uint32_t* low, uint32_t* high);
@@ -181,6 +182,7 @@ public:
// Find information about free space
uint64_t FindFirstAvailable(uint64_t start = 0);
uint64_t FindFirstUsedLBA(void);
+ uint64_t FindLastUsedLBA(void);
uint64_t FindFirstInLargest(void);
uint64_t FindLastAvailable();
uint64_t FindLastInFree(uint64_t start, bool align = false);
diff --git a/gptcl.cc b/gptcl.cc
index 34c9421..f6517e4 100644
--- a/gptcl.cc
+++ b/gptcl.cc
@@ -68,7 +68,7 @@ int GPTDataCL::DoOptions(int argc, char* argv[]) {
int opt, numOptions = 0, saveData = 0, neverSaveData = 0;
int partNum = 0, newPartNum = -1, saveNonGPT = 1, retval = 0, pretend = 0;
int byteSwapPartNum = 0;
- uint64_t low, high, startSector, endSector, sSize, mainTableLBA;
+ uint64_t low, high, startSector, endSector, sSize, mainTableLBA, secondTableLBA;
uint64_t temp; // temporary variable; free to use in any case
char *device;
string cmd, typeGUID, name;
@@ -94,7 +94,8 @@ int GPTDataCL::DoOptions(int argc, char* argv[]) {
{"hybrid", 'h', POPT_ARG_STRING, &hybrids, 'h', "create hybrid MBR", "partnum[:partnum...][:EE]"},
{"info", 'i', POPT_ARG_INT, &infoPartNum, 'i', "show detailed information on partition", "partnum"},
{"align-end", 'I', POPT_ARG_NONE, NULL, 'I', "align partition end points", ""},
- {"move-main-table", 'j', POPT_ARG_INT, &mainTableLBA, 'j', "adjust the location of the main partition table", "sector"},
+ {"move-main-table", 'j', POPT_ARG_INT, &mainTableLBA, 'j', "change the start sector of the main partition table", "sector"},
+ {"move-secondary-table", 'k', POPT_ARG_INT, &secondTableLBA, 'k', "change the start sector of the secondary partition table", "sector"},
{"load-backup", 'l', POPT_ARG_STRING, &backupFile, 'l', "load GPT backup from file", "file"},
{"list-types", 'L', POPT_ARG_NONE, NULL, 'L', "list known partition types", ""},
{"gpttombr", 'm', POPT_ARG_STRING, &mbrParts, 'm', "convert GPT to MBR", "partnum[:partnum...]"},
@@ -117,6 +118,7 @@ int GPTDataCL::DoOptions(int argc, char* argv[]) {
{"zap", 'z', POPT_ARG_NONE, NULL, 'z', "zap (destroy) GPT (but not MBR) data structures", ""},
{"zap-all", 'Z', POPT_ARG_NONE, NULL, 'Z', "zap (destroy) GPT and MBR data structures", ""},
POPT_AUTOHELP { NULL, 0, 0, NULL, 0 }
+ // TODO: Incorrect(ly documented) (long) arguments are silently swallowed and seem to take the next argument with them!
};
// Create popt context...
@@ -280,13 +282,21 @@ int GPTDataCL::DoOptions(int argc, char* argv[]) {
alignEnd = true;
break;
case 'j':
- if (MoveMainTable(mainTableLBA)) {
- JustLooking(0);
- saveData = 1;
- } else {
- neverSaveData = 1;
- } // if/else
- break;
+ if (MoveMainTable(mainTableLBA)) {
+ JustLooking(0);
+ saveData = 1;
+ } else {
+ neverSaveData = 1;
+ } // if/else
+ break;
+ case 'k':
+ if (MoveSecondaryTable(secondTableLBA)) {
+ JustLooking(0);
+ saveData = 1;
+ } else {
+ neverSaveData = 1;
+ } // if/else
+ break;
case 'l':
LoadBackupFile(backupFile, saveData, neverSaveData);
free(backupFile);
diff --git a/sgdisk.8 b/sgdisk.8
index b966a13..dad877b 100644
--- a/sgdisk.8
+++ b/sgdisk.8
@@ -304,7 +304,7 @@ with the current final partition being aligned, and if \fBsgdisk\fR is asked
to create a partition in that space, then it will \fBnot\fR be end\-aligned.
.TP
-.B \-j, \-\-adjust\-main\-table=sector
+.B \-j, \-\-move\-main\-table=sector
Adjust the location of the main partition table. This value is normally 2,
but it may need to be increased in some cases, such as when a
system\-on\-chip (SoC) is hard\-coded to read boot code from sector 2. I
diff --git a/sgdisk.html b/sgdisk.html
index 36a28bc..ec0f505 100644
--- a/sgdisk.html
+++ b/sgdisk.html
@@ -195,7 +195,7 @@ when using this option. The others require a partition number. The
<I>nand</I>, <I>xor</I>, <I>=</I>, <I>set</I>, <I>clear</I>, and
<I>toggle</I> options enable you to change the attribute bit value. The
<I>set</I>, <I>clear</I>, <I>toggle</I>, and <I>get</I> options work on a
-bit number; the others work on a hexadecimal bit mask. For example, type
+bit number; the others work on a hexadecimal bit mask. For example, type
<B>sgdisk -A 4:set:2 /dev/sdc</B> to set the bit 2 attribute (legacy BIOS
bootable) on partition 4 on <I>/dev/sdc</I>.
<P>
@@ -344,7 +344,7 @@ if the free space at the end of a disk is less than the alignment value,
with the current final partition being aligned, and if <B>sgdisk</B> is asked
to create a partition in that space, then it will <B>not</B> be end-aligned.
<P>
-<DT><B>-j, --adjust-main-table=sector</B>
+<DT><B>-j, --move-main-table=sector</B>
<DD>
Adjust the location of the main partition table. This value is normally 2,