nixos-23.11: fileSystems.*.format{Options->Args},

other fixes
This commit is contained in:
Niklas Gollenstede 2023-11-27 17:15:44 +01:00
parent 2bbefc97c3
commit afc4c9ab9a
15 changed files with 67 additions and 51 deletions

View File

@ -153,6 +153,7 @@
"reexec", // option
"refreservation", // zfs
"relatime", // mount option
"rootfs", // linux
"rpool", // zfs
"sandboxing", // word
"sata", // storage protocol

View File

@ -14,7 +14,5 @@
installer = "installer"; # config.${installer}
setup = "setup"; # config.${setup}
preface = "preface"; # config.${preface}
extlinux = "extlinux"; # config.boot.loader.${extlinux}
preMountCommands = "preMountCommands"; # config.fileSystems.*.${preMountCommands}
};
}; }

Binary file not shown.

View File

@ -2,7 +2,7 @@
"Fully automated NixOS CLI installer"
); inputs = {
nixpkgs = { url = "github:NixOS/nixpkgs/nixos-23.05"; };
nixpkgs = { url = "github:NixOS/nixpkgs/nixos-23.11"; };
functions = { url = "github:NiklasGollenstede/nix-functions"; inputs.nixpkgs.follows = "nixpkgs"; };
config.url = "path:./example/defaultConfig";

View File

@ -53,8 +53,8 @@ in { preface = { # (any »preface« options have to be defined here)
# Put everything except for /boot and /nix/store on a tmpfs. This is the absolute minimum, most usable systems require some more paths that are persistent (e.g. all of /nix and /home).
fileSystems."/" = { fsType = "tmpfs"; device = "tmpfs"; neededForBoot = true; options = [ "mode=755" ]; };
fileSystems."/boot" = { fsType = "vfat"; device = "/dev/disk/by-partlabel/boot-${hash}"; neededForBoot = true; options = [ "noatime" ]; formatOptions = "-F 32"; };
fileSystems."/system" = { fsType = "ext4"; device = "/dev/disk/by-partlabel/system-${hash}"; neededForBoot = true; options = [ "noatime" ]; formatOptions = "-O inline_data -E nodiscard -F"; };
fileSystems."/boot" = { fsType = "vfat"; device = "/dev/disk/by-partlabel/boot-${hash}"; neededForBoot = true; options = [ "noatime" ]; formatArgs = [ "-F" "32" ]; };
fileSystems."/system" = { fsType = "ext4"; device = "/dev/disk/by-partlabel/system-${hash}"; neededForBoot = true; options = [ "noatime" ]; formatArgs = [ "-O" "inline_data" "-E" "nodiscard" "-F" ]; };
fileSystems."/nix/store" = { options = ["bind,ro"]; device = "/system/nix/store"; neededForBoot = true; };

View File

@ -100,7 +100,7 @@ in rec {
# 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.
inputs ? { },
# Arguments »{ files, dir, exclude, }« to »mkNixosConfigurations«, see there for details. May also be a list of those attrsets, in which case those multiple sets of hosts will be built separately by »mkNixosConfigurations«, allowing for separate sets of »peers« passed to »mkNixosConfiguration«. Each call will receive all other arguments, and the resulting sets of hosts will be merged.
systems ? ({ dir = "${inputs.self}/hosts"; exclude = [ ]; }),
systems ? ({ dir = "${inputs.self}/hosts"; exclude = [ ]; }), # TODO: (before nix 2.14) this is not relative to the flake.nix, but relative to the root of the repo
# List of Modules to import for all hosts, in addition to the default ones in »nixpkgs«. The host-individual module should selectively enable these. Defaults to ».nixosModules.default« of all »moduleInputs«/»inputs« (including »inputs.self«).
modules ? (getModulesFromInputs moduleInputs),
# (Subset of) »inputs« that »modules« will be used from. Example: »{ inherit (inputs) self flakeA flakeB; }«.

View File

@ -197,9 +197,8 @@ function format-partitions {
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[@]}" 1>&2 ; \return 1 ; fi
else continue ; fi
#if [[ ${fs[fsType]} == ext4 && ' '${fs[formatOptions]}' ' != *' -F '* ]] ; then fs[formatOptions]+=' -F' ; fi
#if [[ ${fs[fsType]} == f2fs && ' '${fs[formatOptions]}' ' != *' -f '* ]] ; then fs[formatOptions]+=' -f' ; 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]}" >$beLoud 2>$beSilent ) || return
eval 'declare -a formatArgs='"${fs[formatArgs]}"
( PATH=@{native.e2fsprogs}/bin:@{native.f2fs-tools}/bin:@{native.xfsprogs}/bin:@{native.dosfstools}/bin:$PATH ; ${_set_x:-:} ; mkfs."${fs[fsType]}" "${formatArgs[@]}" "${fs[device]}" >$beLoud 2>$beSilent ) || return
@{native.parted}/bin/partprobe "${fs[device]}" || true
done
for swapDev in "@{config.swapDevices!catAttrs.device[@]}" ; do

View File

@ -121,8 +121,8 @@ function nixos-install-cmd {( # 1: mnt, 2: topLevel
#PATH=@{native.nix}/bin:$PATH:@{config.systemd.package}/bin TMPDIR=/tmp LC_ALL=C @{native.nixos-install-tools}/bin/nixos-install --system "$2" --no-root-passwd --no-channel-copy --root "$1" || exit # We did most of this, so just install the bootloader:
export NIXOS_INSTALL_BOOTLOADER=1 # tells some bootloader installers (systemd & grub) to not skip parts of the installation
#( export LC_ALL=C ; PATH=$PATH:@{native.util-linux}/bin:@{native.nixos-install-tools}/bin/ ; ${_set_x:-:} ; nixos-enter --silent --root "$1" -- @{config.system.build.installBootLoader} "$2" ) || exit
LC_ALL=C PATH=$PATH:@{native.util-linux}/bin @{native.nixos-install-tools}/bin/nixos-enter --silent --root "$1" -c "${_set_x:-:} ; @{config.system.build.installBootLoader} $2" || exit
LC_ALL=C PATH=@{native.busybox}/bin:$PATH:@{native.util-linux}/bin @{native.nixos-install-tools}/bin/nixos-enter --silent --root "$1" -c "source /etc/set-environment ; ${_set_x:-:} ; @{config.system.build.installBootLoader} $2" || exit
# (newer versions of »mount« seem to be unable to do »--make-private« on »rootfs« (in the initrd), but busybox's mount still works)
)}
declare-flag install-system toplevel "store-path" "Optional replacement for the actual »config.system.build.toplevel«."
@ -142,7 +142,7 @@ function install-system-to {( set -u # 1: mnt, 2?: topLevel
mkdir -p -m 755 $mnt/nix/var/nix || exit ; mkdir -p -m 1775 $mnt/nix/store || exit
mkdir -p $mnt/etc $mnt/run || exit ; mkdir -p -m 1777 $mnt/tmp || exit
@{native.util-linux}/bin/mount tmpfs -t tmpfs $mnt/run || exit ; prepend_trap "@{native.util-linux}/bin/umount -l $mnt/run" EXIT || exit # If there isn't anything mounted here, »activate« will mount a tmpfs (inside »nixos-enter«'s private mount namespace). That would hide the additions below.
[[ -e $mnt/etc/NIXOS ]] || touch $mnt/etc/NIXOS || exit # for »switch-to-configuration«
[[ -e $mnt/etc/NIXOS ]] || touch $mnt/etc/NIXOS || exit # for »nixos-enter«
[[ -e $mnt/etc/mtab ]] || ln -sfn /proc/mounts $mnt/etc/mtab || exit
ln -sT $( realpath $targetSystem ) $mnt/run/current-system || exit
#mkdir -p /nix/var/nix/db # »nixos-containers« requires this but nothing creates it before nix is used. BUT »nixos-enter« screams: »/nix/var/nix/db exists and is not a regular file.«
@ -200,7 +200,7 @@ function install-system-to {( set -u # 1: mnt, 2?: topLevel
else
( set +x ; echo "Installation done! This shell is in a chroot in the mounted system for inspection. Exiting the shell will unmount the system." 1>&2 )
fi
LC_ALL=C PATH=$PATH:@{native.util-linux}/bin @{native.nixos-install-tools}/bin/nixos-enter --root $mnt -- /nix/var/nix/profiles/system/sw/bin/bash -c 'source /etc/set-environment ; NIXOS_INSTALL_BOOTLOADER=1 CHROOT_DIR="'"$mnt"'" mnt=/ exec "'"$self"'" bash' || exit # +o monitor
LC_ALL=C PATH=@{native.busybox}/bin:$PATH:@{native.util-linux}/bin @{native.nixos-install-tools}/bin/nixos-enter --root $mnt -- /nix/var/nix/profiles/system/sw/bin/bash -c 'source /etc/set-environment ; NIXOS_INSTALL_BOOTLOADER=1 CHROOT_DIR="'"$mnt"'" mnt=/ exec "'"$self"'" bash' || exit # +o monitor
fi
mkdir -p $mnt/var/lib/systemd/timesync && touch $mnt/var/lib/systemd/timesync/clock || true # save current time

View File

@ -16,13 +16,13 @@ This uses the same implementation as `boot.loader.generic-extlinux-compatible` t
```nix
#*/# end of MarkDown, beginning of NixOS module:
dirname: inputs: args@{ config, options, pkgs, lib, ... }: let lib = inputs.self.lib.__internal__; in let
inherit (inputs.config.rename) setup extlinux;
cfg = config.boot.loader.${extlinux};
inherit (inputs.config.rename) setup;
cfg = config.boot.loader.extlinux;
targetMount = let path = lib.findFirst (path: config.fileSystems?${path}) "/" (lib.fun.parentPaths cfg.targetDir); in config.fileSystems.${path};
supportedFSes = [ "vfat" "ntfs" "ext2" "ext3" "ext4" "btrfs" "xfs" "ufs" ]; fsSupported = fs: builtins.elem fs supportedFSes;
in {
options = { boot.loader.${extlinux} = {
options = { boot.loader.extlinux = {
enable = lib.mkEnableOption (lib.mdDoc ''
`extlinux`, a simple bootloader for legacy-BIOS environments, like (by default) Qemu.
This uses the same implementation as `boot.loader.generic-extlinux-compatible` to generate the bootloader configuration, but then actually also installs `extlinux` itself, instead of relying on something else (like an externally installed u-boot) to read and execute the configuration.
@ -57,17 +57,17 @@ in {
assertions = [ {
assertion = cfg.allowInstableTargetPart || (builtins.match ''^/dev/disk/by-(id|label|partlabel|partuuid|uuid)/.*[^/]$'' cfg.targetPart) != null;
message = ''
`config.boot.loader.${extlinux}.targetPart` is set to `${cfg.targetPart}`, which is not a stable path in `/dev/disk/by-{id,label,partlabel,partuuid,uuid}/`. Not using a unique identifier (or even using a path that can unexpectedly change) is very risky.
`config.boot.loader.extlinux.targetPart` is set to `${cfg.targetPart}`, which is not a stable path in `/dev/disk/by-{id,label,partlabel,partuuid,uuid}/`. Not using a unique identifier (or even using a path that can unexpectedly change) is very risky.
'';
} {
assertion = fsSupported targetMount.fsType;
message = ''
`config.boot.loader.${extlinux}.targetPart`'s closest mount (`${targetMount.mountPoint}`) is of type `${targetMount.fsType}`, which is not one of extlinux's supported types (${lib.concatStringsSep ", " supportedFSes}).
`config.boot.loader.extlinux.targetPart`'s closest mount (`${targetMount.mountPoint}`) is of type `${targetMount.fsType}`, which is not one of extlinux's supported types (${lib.concatStringsSep ", " supportedFSes}).
'';
} ];
${setup}.bootpart = { enable = lib.mkDefault true; mountpoint = lib.mkDefault cfg.targetDir; };
boot.loader.${extlinux}.allowInstableTargetPart = lib.mkForce false;
boot.loader.extlinux.allowInstableTargetPart = lib.mkForce false;
system.boot.loader.id = "extlinux";
system.build.installBootLoader = "${pkgs.writeShellScript "install-extlinux.sh" ''
@ -107,7 +107,7 @@ in {
}) (
(lib.mkIf (options.virtualisation?useDefaultFilesystems) { # (»nixos/modules/virtualisation/qemu-vm.nix« is imported, i.e. we are building a "vmVariant")
boot.loader.${extlinux} = {
boot.loader.extlinux = {
enable = lib.mkIf config.virtualisation.useDefaultFilesystems (lib.mkVMOverride false);
allowInstableTargetPart = lib.mkVMOverride true; # (»/dev/sdX« etc in the VM are stable (if the VM is invoked the same way))
};

View File

@ -0,0 +1 @@
dirname: inputs@{ self, nixpkgs, ...}: self.lib.__internal__.fun.importModules inputs dirname { }

View File

@ -0,0 +1,21 @@
/*
# `fileSystems.*.formatArgs`
## Implementation
```nix
#*/# end of MarkDown, beginning of NixOS module:
dirname: inputs: moduleArgs@{ config, pkgs, lib, utils, ... }: let lib = inputs.self.lib.__internal__; in let
inherit (inputs.config.rename) preMountCommands;
in {
options = {
fileSystems = lib.mkOption { type = lib.types.attrsOf (lib.types.submodule [ ({ config, ...}@_: { options = {
formatArgs = lib.mkOption { description = "Arguments passed to mkfs for this filesystem during OS installation."; type = lib.types.listOf lib.types.str; default = if (lib.isString config.formatOptions or null) then lib.splitString config.formatOptions else [ ]; };
}; }) ]);
}; };
# (These are used in »../../lib/setup-scripts/disk.sh#format-partitions«.)
}

View File

@ -1,28 +1,24 @@
/*
# Additions to `fileSystems`
Currently, this just adds `preMountCommands`.
# `fileSystems.*.preMountCommands`
## Implementation
```nix
#*/# end of MarkDown, beginning of NixOS module:
dirname: inputs: moduleArgs@{ config, pkgs, lib, utils, ... }: let lib = inputs.self.lib.__internal__; in let
inherit (inputs.config.rename) preMountCommands;
in {
options = {
fileSystems = lib.mkOption { type = lib.types.attrsOf (lib.types.submodule [ { options = {
${preMountCommands} = lib.mkOption { description = ''
preMountCommands = lib.mkOption { description = ''
Commands to be run as root every time before mounting this filesystem **via systemd**, but after all its dependents were mounted.
This does not order itself before or after `systemd-fsck@''${utils.escapeSystemdPath device}.service`.
This is not implemented for mounts in the initrd (those that are `neededForBoot`) yet.
Note that if a symlink exists at a mount point when systemd's fstab-generator runs, it will read/resolve the symlink and use the link's target as the mount point, resulting in mismatching unit names for that mount, effectively disabling its `.${preMountCommands}`.
Note that if a symlink exists at a mount point when systemd's fstab-generator runs, it will read/resolve the symlink and use the link's target as the mount point, resulting in mismatching unit names for that mount, effectively disabling its `.preMountCommands`.
This does not (apparently and unfortunately) run when mounting via the `mount` command (and probably not with the `mount` system call either).
''; type = lib.types.lines; default = ""; };
#Also, trying to create the "device" of a "nofail" mount will not work with `mount`, as it will not even attempt to mount anything (and thus not run the `.${preMountCommands}`) if the "device" is missing.
#Also, trying to create the "device" of a "nofail" mount will not work with `mount`, as it will not even attempt to mount anything (and thus not run the `.preMountCommands`) if the "device" is missing.
}; } ]);
}; };
@ -30,12 +26,12 @@ in {
in ({
assertions = lib.mapAttrsToList (name: fs: {
assertion = (fs.${preMountCommands} == "") || (!utils.fsNeededForBoot fs);
message = ''The filesystem "${name}" has `.${preMountCommands}` but is also (possibly implicitly) `.neededForBoot`. This is not currently supported.'';
assertion = (fs.preMountCommands == "") || (!utils.fsNeededForBoot fs);
message = ''The filesystem "${name}" has `.preMountCommands` but is also (possibly implicitly) `.neededForBoot`. This is not currently supported.'';
}) config.fileSystems;
# The implementation is derived from the "mkfs-${device'}" service in nixpkgs.
systemd.services = lib.fun.mapMergeUnique (_: args@{ mountPoint, device, depends, ... }: if (args.${preMountCommands} != "") then let
systemd.services = lib.fun.mapMergeUnique (_: args@{ mountPoint, device, depends, ... }: if (args.preMountCommands != "") then let
isDevice = lib.fun.startsWith "/dev/" device;
mountPoint' = utils.escapeSystemdPath mountPoint;
device' = utils.escapeSystemdPath device;
@ -45,7 +41,7 @@ in {
requires = lib.optional isDevice "${device'}.device"; after = lib.optional isDevice "${device'}.device";
unitConfig.RequiresMountsFor = depends ++ [ (builtins.dirOf device) (builtins.dirOf mountPoint) ];
unitConfig.DefaultDependencies = false;
serviceConfig.Type = "oneshot"; script = args.${preMountCommands};
serviceConfig.Type = "oneshot"; script = args.preMountCommands;
}; } else { }) config.fileSystems;
});

View File

@ -32,7 +32,7 @@ in {
a;1 # active/boot ; part1
''; }; };
};
fileSystems.${cfg.mountpoint} = { fsType = "vfat"; device = "/dev/disk/by-partlabel/boot-${hash}"; neededForBoot = true; options = [ "nosuid" "nodev" "noexec" "noatime" "umask=0027" "discard" ]; formatOptions = "-F 32"; };
fileSystems.${cfg.mountpoint} = { fsType = "vfat"; device = "/dev/disk/by-partlabel/boot-${hash}"; neededForBoot = true; options = [ "nosuid" "nodev" "noexec" "noatime" "umask=0027" "discard" ]; formatArgs = [ "-F" "32" ]; };
}) ]);

View File

@ -86,7 +86,7 @@ in let module = {
};
# Create and populate keystore during installation:
fileSystems.${keystore} = { fsType = "vfat"; device = "/dev/mapper/keystore-${hash}"; options = [ "ro" "nosuid" "nodev" "noexec" "noatime" "umask=0277" "noauto" ]; formatOptions = ""; };
fileSystems.${keystore} = { fsType = "vfat"; device = "/dev/mapper/keystore-${hash}"; options = [ "ro" "nosuid" "nodev" "noexec" "noatime" "umask=0277" "noauto" ]; formatArgs = [ ]; };
${setup}.disks.partitions."keystore-${hash}" = { type = lib.mkDefault "8309"; order = lib.mkDefault 1375; disk = lib.mkDefault "primary"; size = lib.mkDefault "32M"; };
${installer}.commands.postFormat = ''( : 'Copy the live keystore to its primary persistent location:'

View File

@ -296,14 +296,14 @@ in {
fileSystems.${cfg.local.bind.source} = {
fsType = fsType; device = "/dev/${if encrypted then "mapper" else "disk/by-partlabel"}/local-${hash}";
} // (if fsType == "f2fs" then {
formatOptions = (lib.concatStrings [
" -O extra_attr" # required by other options
",inode_checksum" # enable inode checksum
",sb_checksum" # enable superblock checksum
",compression" # allow compression
formatArgs = [
"-O" "extra_attr" # required by other options
"-O" "inode_checksum" # enable inode checksum
"-O" "sb_checksum" # enable superblock checksum
"-O" "compression" # allow compression
#"-w ?" # "sector size in bytes"
# sector ? segments < section < zone
]);
];
options = optionsToList (cfg.local.mountOptions // {
# F2FS compresses only for performance and wear. The whole uncompressed space is still reserved (in case the file content needs to get replaced by incompressible data in-place). To free the gained space, »ioctl(fd, F2FS_IOC_RELEASE_COMPRESS_BLOCKS)« needs to be called per file, making the file immutable. Nix could do that when moving stuff into the store.
compress_mode = "fs"; # enable compression for all files
@ -313,16 +313,16 @@ in {
discard = true;
});
} else {
formatOptions = (lib.concatStrings [
" -O inline_data" # embed data of small files in the top-level inode
",has_journal,extent,huge_file,flex_bg,metadata_csum,64bit,dir_nlink,extra_isize" # (ext4 default options)
#",lazy_journal_init,lazy_itable_init" # speed up creation (but: Invalid filesystem option set)
" -I 256" # inode size (ext default, allows for timestamps past 2038)
" -i 16384" # create one inode per 16k bytes of disk (ext default)
" -b 4096" # block size (ext default)
" -E nodiscard" # do not trim the whole blockdev upon formatting
" -e panic" # when (critical?) FS errors are detected, reset the system
]); options = optionsToList (cfg.local.mountOptions // {
formatArgs = [
"-O" "inline_data" # embed data of small files in the top-level inode
#"-O" "has_journal,extent,huge_file,flex_bg,metadata_csum,64bit,dir_nlink,extra_isize" # (ext4 defaults, no need to set again)
#"-O" "lazy_journal_init,lazy_itable_init" # speed up creation (but: Invalid filesystem option set)
"-I" "256" # inode size (ext default, allows for timestamps past 2038)
"-i" "16384" # create one inode per 16k bytes of disk (ext default)
"-b" "4096" # block size (ext default)
"-E" "nodiscard" # do not trim the whole blockdev upon formatting
"-e" "panic" # when (critical?) FS errors are detected, reset the system
]; options = optionsToList (cfg.local.mountOptions // {
discard = true;
});
});
@ -342,7 +342,7 @@ in {
) ++ [ (rec {
device = "${cfg.${type}.bind.source}/${source}";
options = optionsToList (cfg.${type}.mountOptions // args.options // { bind = true; });
${preMountCommands} = lib.mkIf (!extraFsConfig.neededForBoot && !(lib.elem target utils.pathsNeededForBoot)) ''
preMountCommands = lib.mkIf (!extraFsConfig.neededForBoot && !(lib.elem target utils.pathsNeededForBoot)) ''
mkdir -pm 000 -- ${lib.escapeShellArg target}
mkdir -pm 000 -- ${lib.escapeShellArg device}
chown ${toString uid}:${toString gid} -- ${lib.escapeShellArg device}