diff --git a/.vscode/settings.json b/.vscode/settings.json index e64f2c1..3647185 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -153,6 +153,7 @@ "reexec", // option "refreservation", // zfs "relatime", // mount option + "rootfs", // linux "rpool", // zfs "sandboxing", // word "sata", // storage protocol diff --git a/example/defaultConfig/flake.nix b/example/defaultConfig/flake.nix index 997dcd1..3198618 100644 --- a/example/defaultConfig/flake.nix +++ b/example/defaultConfig/flake.nix @@ -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} }; }; } diff --git a/flake.lock b/flake.lock index cab798d..a3a335d 100644 Binary files a/flake.lock and b/flake.lock differ diff --git a/flake.nix b/flake.nix index ce8928f..f938638 100644 --- a/flake.nix +++ b/flake.nix @@ -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"; diff --git a/hosts/example.nix.md b/hosts/example.nix.md index f59090a..b8ad034 100644 --- a/hosts/example.nix.md +++ b/hosts/example.nix.md @@ -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; }; diff --git a/lib/nixos.nix b/lib/nixos.nix index c1853ba..f228e9c 100644 --- a/lib/nixos.nix +++ b/lib/nixos.nix @@ -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; }«. diff --git a/lib/setup-scripts/disk.sh b/lib/setup-scripts/disk.sh index ebd2cad..202eb6a 100644 --- a/lib/setup-scripts/disk.sh +++ b/lib/setup-scripts/disk.sh @@ -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 diff --git a/lib/setup-scripts/install.sh b/lib/setup-scripts/install.sh index 38b4521..7c35a01 100644 --- a/lib/setup-scripts/install.sh +++ b/lib/setup-scripts/install.sh @@ -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 diff --git a/modules/bootloader/extlinux.nix.md b/modules/bootloader/extlinux.nix.md index 2209e03..0b8ed8a 100644 --- a/modules/bootloader/extlinux.nix.md +++ b/modules/bootloader/extlinux.nix.md @@ -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)) }; diff --git a/modules/filesystems/default.nix b/modules/filesystems/default.nix new file mode 100644 index 0000000..2c668d7 --- /dev/null +++ b/modules/filesystems/default.nix @@ -0,0 +1 @@ +dirname: inputs@{ self, nixpkgs, ...}: self.lib.__internal__.fun.importModules inputs dirname { } diff --git a/modules/filesystems/format-args.nix.md b/modules/filesystems/format-args.nix.md new file mode 100644 index 0000000..44c6fbf --- /dev/null +++ b/modules/filesystems/format-args.nix.md @@ -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«.) + +} diff --git a/modules/filesystem.nix.md b/modules/filesystems/pre-mount-commands.nix.md similarity index 75% rename from modules/filesystem.nix.md rename to modules/filesystems/pre-mount-commands.nix.md index 33e764a..728deee 100644 --- a/modules/filesystem.nix.md +++ b/modules/filesystems/pre-mount-commands.nix.md @@ -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; }); diff --git a/modules/setup/bootpart.nix.md b/modules/setup/bootpart.nix.md index 98945f1..b47f4db 100644 --- a/modules/setup/bootpart.nix.md +++ b/modules/setup/bootpart.nix.md @@ -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" ]; }; }) ]); diff --git a/modules/setup/keystore.nix.md b/modules/setup/keystore.nix.md index 650c877..86e2e84 100644 --- a/modules/setup/keystore.nix.md +++ b/modules/setup/keystore.nix.md @@ -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:' diff --git a/modules/setup/temproot.nix.md b/modules/setup/temproot.nix.md index 8c8d8d6..561080c 100644 --- a/modules/setup/temproot.nix.md +++ b/modules/setup/temproot.nix.md @@ -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}