mirror of
https://github.com/NiklasGollenstede/nixos-installer.git
synced 2024-11-27 18:33:11 +01:00
add boot.loader.extra-files module, add fileSystems.*.postMountCommands/preUnmountCommands
This commit is contained in:
parent
65c1691644
commit
95aa261987
4
.vscode/settings.json
vendored
4
.vscode/settings.json
vendored
@ -16,6 +16,7 @@
|
||||
"acltype", // zfs
|
||||
"acpi", // abbr
|
||||
"ahci", // abbr
|
||||
"armstub", "armstubs", // cocnat
|
||||
"asDropin", // systemd
|
||||
"ashift", // zfs
|
||||
"askpass", // program
|
||||
@ -56,6 +57,7 @@
|
||||
"dontStrip", // nixos
|
||||
"dontUnpack", // nixos
|
||||
"dosfstools", // package
|
||||
"dotglob", // option
|
||||
"draid", // zfs
|
||||
"e2fsprogs", // package
|
||||
"elif", // abbr (else if)
|
||||
@ -140,6 +142,7 @@
|
||||
"ostype", // virtual box
|
||||
"overlayed", // word
|
||||
"overridable", // word
|
||||
"overscan", // word
|
||||
"ovmf", // package
|
||||
"partlabel", // linux
|
||||
"partprobe", // program / function
|
||||
@ -197,6 +200,7 @@
|
||||
"typecode", // cli arg
|
||||
"uart", "uarts", // serial protocol
|
||||
"uartmode", // virtual box
|
||||
"uboot", // package
|
||||
"udev", // program
|
||||
"udevadm", // program
|
||||
"udptunnel", // program
|
||||
|
@ -23,17 +23,17 @@ See `nix run .#$hostname -- --help` for options and more commands.
|
||||
```nix
|
||||
#*/# end of MarkDown, beginning of NixOS config flake input:
|
||||
dirname: inputs: { config, pkgs, lib, name, ... }: let lib = inputs.self.lib.__internal__; in let
|
||||
#suffix = builtins.head (builtins.match ''example-(.*)'' name); # make differences in config based on this when using »preface.instances«
|
||||
hash = builtins.substring 0 8 (builtins.hashString "sha256" name);
|
||||
in { preface = { # (any »preface« options have to be defined here)
|
||||
instances = [ "example-explicit" "example" "example-minimal" "example-crypt" "example-raidz" ]; # Generate multiple variants of this host, with these »name«s.
|
||||
instances = [ "explicit-fs" "complex-fs" "minimal-setup" "encrypted" "multi-disk-raidz" "rpi" ]; # Generate multiple variants of this host, with these »name«s.
|
||||
}; imports = [ ({ ## Hardware
|
||||
|
||||
nixpkgs.hostPlatform = "x86_64-linux"; system.stateVersion = "22.05";
|
||||
nixpkgs.hostPlatform = if name == "rpi" then "aarch64-linux" else "x86_64-linux"; system.stateVersion = "24.05";
|
||||
|
||||
## What follows is a whole bunch of boilerplate-ish stuff, most of which multiple hosts would have in common and which would thus be moved to one or more modules:
|
||||
|
||||
boot.loader.extlinux.enable = true;
|
||||
boot.loader.extlinux.enable = name != "rpi";
|
||||
boot.loader.grub.enable = false;
|
||||
|
||||
# Example of adding and/or overwriting setup/maintenance functions:
|
||||
#installer.scripts.install-overwrite = { path = ../example/install.sh.md; order = 1500; };
|
||||
@ -41,7 +41,7 @@ in { preface = { # (any »preface« options have to be defined here)
|
||||
boot.initrd.systemd.enable = true;
|
||||
|
||||
|
||||
}) (lib.mkIf (name == "example-explicit") { ## Minimal explicit FS setup
|
||||
}) (lib.mkIf (name == "explicit-fs") { ## Minimal explicit FS setup
|
||||
|
||||
# Declare a boot and system partition.
|
||||
setup.disks.partitions."boot-${hash}" = { type = "ef00"; size = "64M"; index = 1; order = 1500; };
|
||||
@ -60,7 +60,7 @@ in { preface = { # (any »preface« options have to be defined here)
|
||||
fileSystems."/nix/store" = { options = ["bind,ro"]; device = "/system/nix/store"; neededForBoot = true; };
|
||||
|
||||
|
||||
}) (lib.mkIf (name == "example") { ## More complex but automatic FS setup
|
||||
}) (lib.mkIf (name == "complex-fs") { ## More complex but automatic FS setup
|
||||
|
||||
#setup.disks.devices.primary.size = "16G"; # (default)
|
||||
setup.bootpart.enable = true; setup.bootpart.size = "512M";
|
||||
@ -88,7 +88,7 @@ in { preface = { # (any »preface« options have to be defined here)
|
||||
setup.temproot.local.mounts."/var/log" = lib.mkForce null; # example: don't keep logs
|
||||
|
||||
|
||||
}) (lib.mkIf (name == "example-minimal") { ## Minimal automatic FS setup
|
||||
}) (lib.mkIf (name == "minimal-setup") { ## Minimal automatic FS setup
|
||||
|
||||
setup.bootpart.enable = true; # (also set by »boot.loader.extlinux.enable«)
|
||||
setup.temproot.enable = true;
|
||||
@ -98,7 +98,7 @@ in { preface = { # (any »preface« options have to be defined here)
|
||||
setup.temproot.remote.type = "none";
|
||||
|
||||
|
||||
}) (lib.mkIf (name == "example-crypt") { ## Minimal automatic FS setup
|
||||
}) (lib.mkIf (name == "encrypted") { ## Encrypted FS setup
|
||||
|
||||
setup.keystore.enable = true;
|
||||
setup.keystore.keys."luks/keystore-${hash}/0" = "random"; # (this makes little practical sense)
|
||||
@ -117,10 +117,10 @@ in { preface = { # (any »preface« options have to be defined here)
|
||||
#setup.keystore.keys."luks/rpool-${hash}/0" = "random";
|
||||
|
||||
|
||||
}) (lib.mkIf (name == "example-raidz") { ## Multi-disk ZFS setup
|
||||
}) (lib.mkIf (name == "multi-disk-raidz") { ## Multi-disk ZFS setup
|
||||
|
||||
boot.loader.extlinux.enable = lib.mkForce false; # use UEFI boot this time
|
||||
boot.loader.systemd-boot.enable = true; boot.loader.grub.enable = false;
|
||||
boot.loader.systemd-boot.enable = true;
|
||||
|
||||
setup.disks.devices = lib.genAttrs ([ "primary" "raidz1" "raidz2" "raidz3" ]) (name: { size = "16G"; });
|
||||
setup.bootpart.enable = true; setup.bootpart.size = "512M";
|
||||
@ -140,6 +140,39 @@ in { preface = { # (any »preface« options have to be defined here)
|
||||
setup.disks.partitions."rpool-arc-${hash}" = { type = "bf00"; };
|
||||
|
||||
|
||||
}) (lib.optionalAttrs (name == "rpi") { ## Booting on Raspberry PIs
|
||||
# This is mostly a demo for the `extra-files` module, but it does produce an image that boots on a rPI4
|
||||
|
||||
setup.temproot = { enable = true; local.bind.base = "ext4"; remote.type = "none"; };
|
||||
setup.bootpart.enable = true;
|
||||
|
||||
boot.loader.generic-extlinux-compatible.enable = true;
|
||||
boot.loader.extra-files.enable = true;
|
||||
boot.loader.extra-files.files = let
|
||||
fw = files: lib.genAttrs files (file: { source = "${pkgs.raspberrypifw}/share/raspberrypi/boot/${file}"; });
|
||||
in {
|
||||
"config.txt".format = lib.generators.toINI { listsAsDuplicateKeys = true; };
|
||||
"config.txt".text = lib.mkOrder 100 ''
|
||||
# Generated file. Do not edit.
|
||||
'';
|
||||
"config.txt".data.all = {
|
||||
arm_64bit = 1; enable_uart = 1; avoid_warnings = 1;
|
||||
};
|
||||
"config.txt".data.pi4 = {
|
||||
enable_gic = 1; disable_overscan = 1; arm_boost = 1;
|
||||
kernel = "u-boot-rpi4.bin";
|
||||
armstub = "armstub8-gic.bin";
|
||||
};
|
||||
"u-boot-rpi4.bin".source = "${pkgs.ubootRaspberryPi4_64bit}/u-boot.bin";
|
||||
"armstub8-gic.bin".source = "${pkgs.raspberrypi-armstubs}/armstub8-gic.bin";
|
||||
} // (fw [
|
||||
"start4.elf" "fixup4.dat" "bcm2711-rpi-cm4s.dtb" "bcm2711-rpi-400.dtb" "bcm2711-rpi-4-b.dtb" "bcm2711-rpi-cm4.dtb" "bcm2711-rpi-cm4-io.dtb"
|
||||
]);
|
||||
|
||||
#imports = [ "${inputs.nixos-hardware}/raspberry-pi/4" ]; # activating the correct hardware config should help
|
||||
#hardware.deviceTree.filter = "bcm271*.dtb";
|
||||
|
||||
|
||||
}) ({ ## Base Config
|
||||
|
||||
# Some base config:
|
||||
|
@ -23,26 +23,26 @@ dirname: inputs: args@{ config, options, pkgs, lib, ... }: let lib = inputs.self
|
||||
in {
|
||||
|
||||
options = { boot.loader.extlinux = {
|
||||
enable = lib.mkEnableOption (lib.mdDoc ''
|
||||
enable = lib.mkEnableOption ''
|
||||
`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.
|
||||
Any options affecting the config file generation by `boot.loader.generic-extlinux-compatible` apply, but `boot.loader.generic-extlinux-compatible.enable` should not be set to `true`.
|
||||
|
||||
Since the bootloader runs before selecting a generation or specialisation to run, all sub-options, similar to e.g. {option}`boot.loader.timeout`, apply globally to the system, from whichever configuration last applied its bootloader (e.g. by newly `nixos-rebuild switch/boot`ing it or by calling its `.../bin/switch-to-configuration switch/boot`)
|
||||
'');
|
||||
package = lib.mkOption { description = lib.mdDoc ''
|
||||
'';
|
||||
package = lib.mkOption { description = ''
|
||||
The `syslinux` package to install `extlinux` from use.
|
||||
''; type = lib.types.package; default = pkgs.syslinux; defaultText = lib.literalExpression "pkgs.syslinux"; };
|
||||
targetDir = lib.mkOption { description = lib.mdDoc ''
|
||||
targetDir = lib.mkOption { description = ''
|
||||
The path in whose `./extlinux` sub dir `extlinux` will be installed to. When `nixos-rebuild boot/switch` gets called, this or a parent path needs to be mounted from {option}`.targetPart`.
|
||||
''; type = lib.types.strMatching ''^/.*[^/]$''; default = "/boot"; };
|
||||
targetPart = lib.mkOption { description = lib.mdDoc ''
|
||||
targetPart = lib.mkOption { description = ''
|
||||
The `/dev/disk/by-{id,label,partlabel,partuuid,uuid}/*` path of the *disk partition* holding the filesystem that `extlinux` is installed to. This must be formatted with a filesystem that `extlinux` supports and mounted as (a parent of) {option}`.targetDir`. The disk on which the partition lies will have the bootloader section of its MBR replaced by `extlinux`'s.
|
||||
''; type = lib.types.strMatching ''^/.*[^/]$''; default = targetMount.device; };
|
||||
allowInstableTargetPart = lib.mkOption { internal = true; type = lib.types.bool; };
|
||||
showUI = (lib.mkEnableOption (lib.mdDoc ''
|
||||
showUI = (lib.mkEnableOption ''
|
||||
a simple graphical user interface to select the configuration to start during early boot
|
||||
'')) // { default = true; example = false; };
|
||||
'') // { default = true; example = false; };
|
||||
}; };
|
||||
|
||||
config = let
|
||||
|
79
modules/bootloader/extra-files.nix.md
Normal file
79
modules/bootloader/extra-files.nix.md
Normal file
@ -0,0 +1,79 @@
|
||||
/*
|
||||
|
||||
# Extra Boot-Partition Files
|
||||
|
||||
This module allows copying additional files to the boot partition when installing/updating the bootloader.
|
||||
|
||||
|
||||
## Implementation
|
||||
|
||||
```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;
|
||||
cfg = config.boot.loader.extra-files;
|
||||
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.extra-files = {
|
||||
enable = lib.mkEnableOption ''
|
||||
copying of additional files to the boot partition during the system's installation and activation.
|
||||
Note that this modifies the global, non-versioned bootloader state based on the last generation(s) installed / switched to, and that it only ensures the files existence, possibly overwriting previous files, but does not delete files (left by previous generations or configurations)
|
||||
'';
|
||||
|
||||
files = lib.mkOption { description = ''
|
||||
...
|
||||
''; example = lib.literalExpression ''
|
||||
{ "config.txt".text = lib.mkAfter "disable_splash=1";
|
||||
"start4.elf".source = "${pkgs.raspberrypifw}/share/raspberrypi/boot/start4.elf";
|
||||
}
|
||||
''; default = { }; type = lib.types.attrsOf (lib.types.nullOr (lib.types.submodule ({ name, config, ... }: { options = {
|
||||
source = lib.mkOption { description = "Source path of the file."; type = lib.types.path; };
|
||||
text = lib.mkOption { description = "Text lines of the file."; default = null; type = lib.types.nullOr lib.types.lines; };
|
||||
format = lib.mkOption { description = "Formatter to use to transform `.data` into `.text` lines (if `!= null`)."; default = null; type = lib.types.nullOr (lib.types.functionTo lib.types.str); };
|
||||
data = lib.mkOption { description = "Data to serialize to become the files `.text`. A `.format`ter must be set for the file for this to become applicable, and the data assigned must conform to the particular formatters input requirements."; type = (pkgs.formats.json { }).type; };
|
||||
}; config = {
|
||||
text = lib.mkIf (config.format != null) (config.format config.data);
|
||||
}; }))); apply = lib.filterAttrs (k: v: v != null); };
|
||||
|
||||
tree = lib.mkOption { internal = true; readOnly = true; type = lib.types.package; };
|
||||
|
||||
targetDir = lib.mkOption { description = ''
|
||||
The where the files will be installed (copied) to. This has to be mounted when `nixos-rebuild boot/switch` gets called.
|
||||
''; type = lib.types.strMatching ''^/.*[^/]$''; default = "/boot"; };
|
||||
|
||||
}; } // {
|
||||
|
||||
system.build.installBootLoader = lib.mkOption { apply = old: if !cfg.enable then old else pkgs.writeShellScript "${old.name or "install-bootloader"}-extra-files" ''
|
||||
${old} "$@" || exit
|
||||
shopt -s dotglob # for the rest of the script, have globs (*) also match hidden files
|
||||
function copy () { # 1: src, 2: dst
|
||||
cd "$1" ; for name in * ; do
|
||||
if [[ -d "$1"/"$name" ]] ; then
|
||||
rm "$2"/"$name" 2>/dev/null || true
|
||||
mkdir -p "$2"/"$name" ; copy "$1"/"$name" "$2"/"$name"
|
||||
else
|
||||
if ! ${pkgs.diffutils}/bin/cmp --quiet "$1"/"$name" "$2"/"$name" ; then
|
||||
cp -a "$1"/"$name" "$2"/"$name"
|
||||
fi
|
||||
fi
|
||||
done
|
||||
}
|
||||
copy ${cfg.tree} ${lib.escapeShellArg cfg.targetDir}
|
||||
''; };
|
||||
|
||||
};
|
||||
|
||||
config = {
|
||||
|
||||
# This copies referenced files to reduce the installation's closure size.
|
||||
boot.loader.extra-files.tree = lib.fun.writeTextFiles pkgs "boot-files" { checkPhase = ''
|
||||
${lib.concatStringsSep "\n" (lib.mapAttrsToList (path: file: ''
|
||||
path=${lib.escapeShellArg path} ; mkdir -p "$( dirname "$path" )"
|
||||
cp -aT ${lib.escapeShellArg file.source} "$path"
|
||||
'') (lib.filterAttrs (__: _:_.text == null) cfg.files))}
|
||||
''; } (lib.fun.catAttrSets "text" (lib.filterAttrs (__: _:_.text != null) cfg.files));
|
||||
|
||||
};
|
||||
}
|
@ -17,6 +17,13 @@ in {
|
||||
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 = ""; };
|
||||
postMountCommands = lib.mkOption { description = ''
|
||||
Like `.preMountCommands`, but runs after mounting the filesystem.
|
||||
''; type = lib.types.lines; default = ""; };
|
||||
preUnmountCommands = lib.mkOption { description = ''
|
||||
Like `.preMountCommands`, but runs before unmounting the filesystem.
|
||||
This will only run before unmounting when the FS is unmounted by systemd before the FS is unmounted
|
||||
''; type = lib.types.lines; default = ""; };
|
||||
postUnmountCommands = lib.mkOption { description = ''
|
||||
Like `.preMountCommands`, but runs after unmounting the filesystem.
|
||||
''; type = lib.types.lines; default = ""; };
|
||||
@ -32,27 +39,40 @@ in {
|
||||
}) config.fileSystems);
|
||||
|
||||
# The implementation is derived from the "mkfs-${device'}" service in nixpkgs.
|
||||
services = initrd: lib.fun.mapMergeUnique (_: fs@{ mountPoint, device, depends, ... }: if
|
||||
(fs.preMountCommands != "" || fs.postUnmountCommands != "") && initrd == utils.fsNeededForBoot fs
|
||||
then let
|
||||
mkServices = inInitrd: lib.fun.mapMergeUnique (_: fs@{ mountPoint, device, depends, ... }: let
|
||||
isDevice = lib.fun.startsWith "/dev/" device;
|
||||
mountPoint' = utils.escapeSystemdPath mountPoint;
|
||||
mountPoint' = utils.escapeSystemdPath mountPoint; mountPointDep = [ "${mountPoint'}.mount" ];
|
||||
device' = utils.escapeSystemdPath device;
|
||||
in { "pre-mount-${mountPoint'}" = rec { # TODO: in initrd (or during installation), how to deal with the fact that the system is not mounted at "/"?
|
||||
description = "Prepare mounting ${device} at ${mountPoint}";
|
||||
wantedBy = [ "${mountPoint'}.mount" ]; before = wantedBy; partOf = wantedBy;
|
||||
requires = lib.optional isDevice "${device'}.device"; after = lib.optional isDevice "${device'}.device";
|
||||
unitConfig.RequiresMountsFor = map utils.escapeSystemdExecArg (depends ++ (lib.optional (lib.hasPrefix "/" device) device) ++ [ (builtins.dirOf mountPoint) ]);
|
||||
unitConfig.DefaultDependencies = false; restartIfChanged = false;
|
||||
serviceConfig.Type = "oneshot"; serviceConfig.RemainAfterExit = true;
|
||||
script = lib.mkIf (fs.preMountCommands != "") fs.preMountCommands;
|
||||
preStop = lib.mkIf (fs.postUnmountCommands != "") fs.postUnmountCommands; # ("preStop" still runs post unmount)
|
||||
}; } else { }) config.fileSystems;
|
||||
mkService = when: { # TODO: in initrd (or during installation), how to deal with the fact that the system is mounted at "/sysroot"?
|
||||
description = "Prepare mounting ${device} at ${mountPoint}";
|
||||
wantedBy = mountPointDep; ${if when != "after" then when else null} = mountPointDep; partOf = mountPointDep;
|
||||
requires = lib.optional isDevice "${device'}.device"; after = (lib.optionals (when == "after") mountPointDep) ++ (lib.optional isDevice "${device'}.device");
|
||||
unitConfig.RequiresMountsFor = map utils.escapeSystemdExecArg (depends ++ (lib.optional (lib.hasPrefix "/" device) device) ++ [ (builtins.dirOf mountPoint) ]);
|
||||
unitConfig.DefaultDependencies = false; restartIfChanged = false;
|
||||
serviceConfig.Type = "oneshot"; serviceConfig.RemainAfterExit = true;
|
||||
};
|
||||
prepare = ''
|
||||
#set -x
|
||||
'';
|
||||
in lib.optionalAttrs (inInitrd == utils.fsNeededForBoot fs) (
|
||||
(lib.optionalAttrs (
|
||||
fs.preMountCommands != "" || fs.postUnmountCommands != ""
|
||||
) { "${mountPoint'}-before" = (mkService "before") // {
|
||||
script = lib.mkIf (fs.preMountCommands != "") (prepare + fs.preMountCommands);
|
||||
preStop = lib.mkIf (fs.postUnmountCommands != "") (prepare + fs.postUnmountCommands); # ("preStop" still runs post unmount)
|
||||
}; })
|
||||
// (lib.optionalAttrs (
|
||||
fs.postMountCommands != "" || fs.preUnmountCommands != ""
|
||||
) { "${mountPoint'}-after" = (mkService "after") // {
|
||||
script = lib.mkIf (fs.postMountCommands != "") (prepare + fs.postMountCommands);
|
||||
preStop = lib.mkIf (fs.preUnmountCommands != "") (prepare + fs.preUnmountCommands);
|
||||
}; })
|
||||
)) config.fileSystems;
|
||||
|
||||
in {
|
||||
inherit assertions;
|
||||
systemd.services = services false;
|
||||
boot.initrd.systemd.services = lib.mkIf (config.boot.initrd.systemd.enable) (services true);
|
||||
systemd.services = mkServices false;
|
||||
boot.initrd.systemd.services = lib.mkIf (config.boot.initrd.systemd.enable) (mkServices true);
|
||||
};
|
||||
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user