diff options
Diffstat (limited to 'nixos/modules/system/boot')
-rw-r--r-- | nixos/modules/system/boot/binfmt.nix | 4 | ||||
-rw-r--r-- | nixos/modules/system/boot/grow-partition.nix | 3 | ||||
-rw-r--r-- | nixos/modules/system/boot/initrd-network.nix | 64 | ||||
-rw-r--r-- | nixos/modules/system/boot/initrd-ssh.nix | 178 | ||||
-rw-r--r-- | nixos/modules/system/boot/kernel.nix | 273 | ||||
-rw-r--r-- | nixos/modules/system/boot/loader/grub/grub.nix | 10 | ||||
-rw-r--r-- | nixos/modules/system/boot/loader/grub/install-grub.pl | 8 | ||||
-rw-r--r-- | nixos/modules/system/boot/loader/grub/memtest.nix | 57 | ||||
-rw-r--r-- | nixos/modules/system/boot/loader/init-script/init-script-builder.sh | 2 | ||||
-rw-r--r-- | nixos/modules/system/boot/loader/loader.nix | 7 | ||||
-rw-r--r-- | nixos/modules/system/boot/luksroot.nix | 84 | ||||
-rw-r--r-- | nixos/modules/system/boot/networkd.nix | 218 | ||||
-rw-r--r-- | nixos/modules/system/boot/stage-1-init.sh | 19 | ||||
-rw-r--r-- | nixos/modules/system/boot/stage-1.nix | 54 | ||||
-rw-r--r-- | nixos/modules/system/boot/systemd-lib.nix | 26 | ||||
-rw-r--r-- | nixos/modules/system/boot/systemd-nspawn.nix | 4 | ||||
-rw-r--r-- | nixos/modules/system/boot/systemd.nix | 70 |
17 files changed, 771 insertions, 310 deletions
diff --git a/nixos/modules/system/boot/binfmt.nix b/nixos/modules/system/boot/binfmt.nix index a32c9dc1f2b..a677ab4cb71 100644 --- a/nixos/modules/system/boot/binfmt.nix +++ b/nixos/modules/system/boot/binfmt.nix @@ -134,6 +134,10 @@ let }; in { + imports = [ + (lib.mkRenamedOptionModule [ "boot" "binfmtMiscRegistrations" ] [ "boot" "binfmt" "registrations" ]) + ]; + options = { boot.binfmt = { registrations = mkOption { diff --git a/nixos/modules/system/boot/grow-partition.nix b/nixos/modules/system/boot/grow-partition.nix index 8c9b1502558..71a86c74772 100644 --- a/nixos/modules/system/boot/grow-partition.nix +++ b/nixos/modules/system/boot/grow-partition.nix @@ -7,6 +7,9 @@ with lib; { + imports = [ + (mkRenamedOptionModule [ "virtualisation" "growPartition" ] [ "boot" "growPartition" ]) + ]; options = { boot.growPartition = mkEnableOption "grow the root partition on boot"; diff --git a/nixos/modules/system/boot/initrd-network.nix b/nixos/modules/system/boot/initrd-network.nix index cb8fc957a99..0ab6e626b34 100644 --- a/nixos/modules/system/boot/initrd-network.nix +++ b/nixos/modules/system/boot/initrd-network.nix @@ -6,7 +6,11 @@ let cfg = config.boot.initrd.network; - dhcpinterfaces = lib.attrNames (lib.filterAttrs (iface: v: v.useDHCP == true) (config.networking.interfaces or {})); + dhcpInterfaces = lib.attrNames (lib.filterAttrs (iface: v: v.useDHCP == true) (config.networking.interfaces or {})); + doDhcp = config.networking.useDHCP || dhcpInterfaces != []; + dhcpIfShellExpr = if config.networking.useDHCP + then "$(ls /sys/class/net/ | grep -v ^lo$)" + else lib.concatMapStringsSep " " lib.escapeShellArg dhcpInterfaces; udhcpcScript = pkgs.writeScript "udhcp-script" '' @@ -62,6 +66,16 @@ in ''; }; + boot.initrd.network.flushBeforeStage2 = mkOption { + type = types.bool; + default = true; + description = '' + Whether to clear the configuration of the interfaces that were set up in + the initrd right before stage 2 takes over. Stage 2 will do the regular network + configuration based on the NixOS networking options. + ''; + }; + boot.initrd.network.udhcpc.extraArgs = mkOption { default = []; type = types.listOf types.str; @@ -89,49 +103,45 @@ in boot.initrd.kernelModules = [ "af_packet" ]; boot.initrd.extraUtilsCommands = '' - copy_bin_and_libs ${pkgs.mkinitcpio-nfs-utils}/bin/ipconfig + copy_bin_and_libs ${pkgs.klibc}/lib/klibc/bin.static/ipconfig ''; boot.initrd.preLVMCommands = mkBefore ( # Search for interface definitions in command line. '' + ifaces="" for o in $(cat /proc/cmdline); do case $o in ip=*) - ipconfig $o && hasNetwork=1 + ipconfig $o && ifaces="$ifaces $(echo $o | cut -d: -f6)" ;; esac done '' # Otherwise, use DHCP. - + optionalString (config.networking.useDHCP || dhcpinterfaces != []) '' - if [ -z "$hasNetwork" ]; then - - # Bring up all interfaces. - for iface in $(ls /sys/class/net/); do - echo "bringing up network interface $iface..." - ip link set "$iface" up - done + + optionalString doDhcp '' + # Bring up all interfaces. + for iface in ${dhcpIfShellExpr}; do + echo "bringing up network interface $iface..." + ip link set "$iface" up && ifaces="$ifaces $iface" + done - # Acquire DHCP leases. - for iface in ${ if config.networking.useDHCP then - "$(ls /sys/class/net/ | grep -v ^lo$)" - else - lib.concatMapStringsSep " " lib.escapeShellArg dhcpinterfaces - }; do - echo "acquiring IP address via DHCP on $iface..." - udhcpc --quit --now -i $iface -O staticroutes --script ${udhcpcScript} ${udhcpcArgs} && hasNetwork=1 - done - fi + # Acquire DHCP leases. + for iface in ${dhcpIfShellExpr}; do + echo "acquiring IP address via DHCP on $iface..." + udhcpc --quit --now -i $iface -O staticroutes --script ${udhcpcScript} ${udhcpcArgs} + done '' - + '' - if [ -n "$hasNetwork" ]; then - echo "networking is up!" - ${cfg.postCommands} - fi - ''); + + cfg.postCommands); + + boot.initrd.postMountCommands = mkIf cfg.flushBeforeStage2 '' + for iface in $ifaces; do + ip address flush "$iface" + ip link down "$iface" + done + ''; }; diff --git a/nixos/modules/system/boot/initrd-ssh.nix b/nixos/modules/system/boot/initrd-ssh.nix index 2d3e3b05c98..60760487a1d 100644 --- a/nixos/modules/system/boot/initrd-ssh.nix +++ b/nixos/modules/system/boot/initrd-ssh.nix @@ -10,19 +10,21 @@ in { - options = { - - boot.initrd.network.ssh.enable = mkOption { + options.boot.initrd.network.ssh = { + enable = mkOption { type = types.bool; default = false; description = '' Start SSH service during initrd boot. It can be used to debug failing boot on a remote server, enter pasphrase for an encrypted partition etc. Service is killed when stage-1 boot is finished. + + The sshd configuration is largely inherited from + <option>services.openssh</option>. ''; }; - boot.initrd.network.ssh.port = mkOption { + port = mkOption { type = types.int; default = 22; description = '' @@ -30,7 +32,7 @@ in ''; }; - boot.initrd.network.ssh.shell = mkOption { + shell = mkOption { type = types.str; default = "/bin/ash"; description = '' @@ -38,95 +40,163 @@ in ''; }; - boot.initrd.network.ssh.hostRSAKey = mkOption { - type = types.nullOr types.path; - default = null; - description = '' - RSA SSH private key file in the Dropbear format. - - WARNING: Unless your bootloader supports initrd secrets, this key is - contained insecurely in the global Nix store. Do NOT use your regular - SSH host private keys for this purpose or you'll expose them to - regular users! - ''; - }; - - boot.initrd.network.ssh.hostDSSKey = mkOption { - type = types.nullOr types.path; - default = null; + hostKeys = mkOption { + type = types.listOf (types.either types.str types.path); + default = []; + example = [ + "/etc/secrets/initrd/ssh_host_rsa_key" + "/etc/secrets/initrd/ssh_host_ed25519_key" + ]; description = '' - DSS SSH private key file in the Dropbear format. - - WARNING: Unless your bootloader supports initrd secrets, this key is - contained insecurely in the global Nix store. Do NOT use your regular - SSH host private keys for this purpose or you'll expose them to - regular users! + Specify SSH host keys to import into the initrd. + + To generate keys, use + <citerefentry><refentrytitle>ssh-keygen</refentrytitle><manvolnum>1</manvolnum></citerefentry>: + + <screen> + <prompt># </prompt>ssh-keygen -t rsa -N "" -f /etc/secrets/initrd/ssh_host_rsa_key + <prompt># </prompt>ssh-keygen -t ed25519 -N "" -f /etc/secrets/initrd/ssh_host_ed25519_key + </screen> + + <warning> + <para> + Unless your bootloader supports initrd secrets, these keys + are stored insecurely in the global Nix store. Do NOT use + your regular SSH host private keys for this purpose or + you'll expose them to regular users! + </para> + <para> + Additionally, even if your initrd supports secrets, if + you're using initrd SSH to unlock an encrypted disk then + using your regular host keys exposes the private keys on + your unencrypted boot partition. + </para> + </warning> ''; }; - boot.initrd.network.ssh.hostECDSAKey = mkOption { - type = types.nullOr types.path; - default = null; - description = '' - ECDSA SSH private key file in the Dropbear format. - - WARNING: Unless your bootloader supports initrd secrets, this key is - contained insecurely in the global Nix store. Do NOT use your regular - SSH host private keys for this purpose or you'll expose them to - regular users! - ''; - }; - - boot.initrd.network.ssh.authorizedKeys = mkOption { + authorizedKeys = mkOption { type = types.listOf types.str; default = config.users.users.root.openssh.authorizedKeys.keys; + defaultText = "config.users.users.root.openssh.authorizedKeys.keys"; description = '' Authorized keys for the root user on initrd. - Note that Dropbear doesn't support OpenSSH's Ed25519 key type. ''; }; - }; - config = mkIf (config.boot.initrd.network.enable && cfg.enable) { + imports = + map (opt: mkRemovedOptionModule ([ "boot" "initrd" "network" "ssh" ] ++ [ opt ]) '' + The initrd SSH functionality now uses OpenSSH rather than Dropbear. + + If you want to keep your existing initrd SSH host keys, convert them with + $ dropbearconvert dropbear openssh dropbear_host_$type_key ssh_host_$type_key + and then set options.boot.initrd.network.ssh.hostKeys. + '') [ "hostRSAKey" "hostDSSKey" "hostECDSAKey" ]; + + config = let + # Nix complains if you include a store hash in initrd path names, so + # as an awful hack we drop the first character of the hash. + initrdKeyPath = path: if isString path + then path + else let name = builtins.baseNameOf path; in + builtins.unsafeDiscardStringContext ("/etc/ssh/" + + substring 1 (stringLength name) name); + + sshdCfg = config.services.openssh; + + sshdConfig = '' + Port ${toString cfg.port} + + PasswordAuthentication no + ChallengeResponseAuthentication no + + ${flip concatMapStrings cfg.hostKeys (path: '' + HostKey ${initrdKeyPath path} + '')} + + KexAlgorithms ${concatStringsSep "," sshdCfg.kexAlgorithms} + Ciphers ${concatStringsSep "," sshdCfg.ciphers} + MACs ${concatStringsSep "," sshdCfg.macs} + + LogLevel ${sshdCfg.logLevel} + + ${if sshdCfg.useDns then '' + UseDNS yes + '' else '' + UseDNS no + ''} + ''; + in mkIf (config.boot.initrd.network.enable && cfg.enable) { assertions = [ - { assertion = cfg.authorizedKeys != []; + { + assertion = cfg.authorizedKeys != []; message = "You should specify at least one authorized key for initrd SSH"; } + + { + assertion = cfg.hostKeys != []; + message = '' + You must now pre-generate the host keys for initrd SSH. + See the boot.initrd.network.ssh.hostKeys documentation + for instructions. + ''; + } ]; boot.initrd.extraUtilsCommands = '' - copy_bin_and_libs ${pkgs.dropbear}/bin/dropbear + copy_bin_and_libs ${pkgs.openssh}/bin/sshd cp -pv ${pkgs.glibc.out}/lib/libnss_files.so.* $out/lib ''; boot.initrd.extraUtilsCommandsTest = '' - $out/bin/dropbear -V + # sshd requires a host key to check config, so we pass in the test's + echo -n ${escapeShellArg sshdConfig} | + $out/bin/sshd -t -f /dev/stdin \ + -h ${../../../tests/initrd-network-ssh/ssh_host_ed25519_key} ''; boot.initrd.network.postCommands = '' echo '${cfg.shell}' > /etc/shells echo 'root:x:0:0:root:/root:${cfg.shell}' > /etc/passwd + echo 'sshd:x:1:1:sshd:/var/empty:/bin/nologin' >> /etc/passwd echo 'passwd: files' > /etc/nsswitch.conf - mkdir -p /var/log + mkdir -p /var/log /var/empty touch /var/log/lastlog - mkdir -p /etc/dropbear + mkdir -p /etc/ssh + echo -n ${escapeShellArg sshdConfig} > /etc/ssh/sshd_config + + echo "export PATH=$PATH" >> /etc/profile + echo "export LD_LIBRARY_PATH=$LD_LIBRARY_PATH" >> /etc/profile mkdir -p /root/.ssh ${concatStrings (map (key: '' echo ${escapeShellArg key} >> /root/.ssh/authorized_keys '') cfg.authorizedKeys)} - dropbear -s -j -k -E -p ${toString cfg.port} ${optionalString (cfg.hostRSAKey == null && cfg.hostDSSKey == null && cfg.hostECDSAKey == null) "-R"} + ${flip concatMapStrings cfg.hostKeys (path: '' + # keys from Nix store are world-readable, which sshd doesn't like + chmod 0600 "${initrdKeyPath path}" + '')} + + /bin/sshd -e ''; - boot.initrd.secrets = - (optionalAttrs (cfg.hostRSAKey != null) { "/etc/dropbear/dropbear_rsa_host_key" = cfg.hostRSAKey; }) // - (optionalAttrs (cfg.hostDSSKey != null) { "/etc/dropbear/dropbear_dss_host_key" = cfg.hostDSSKey; }) // - (optionalAttrs (cfg.hostECDSAKey != null) { "/etc/dropbear/dropbear_ecdsa_host_key" = cfg.hostECDSAKey; }); + boot.initrd.postMountCommands = '' + # Stop sshd cleanly before stage 2. + # + # If you want to keep it around to debug post-mount SSH issues, + # run `touch /.keep_sshd` (either from an SSH session or in + # another initrd hook like preDeviceCommands). + if ! [ -e /.keep_sshd ]; then + pkill -x sshd + fi + ''; + boot.initrd.secrets = listToAttrs + (map (path: nameValuePair (initrdKeyPath path) path) cfg.hostKeys); }; } diff --git a/nixos/modules/system/boot/kernel.nix b/nixos/modules/system/boot/kernel.nix index 8a309f3bc5f..43871f439f7 100644 --- a/nixos/modules/system/boot/kernel.nix +++ b/nixos/modules/system/boot/kernel.nix @@ -101,7 +101,12 @@ in type = types.bool; default = false; description = '' - Whether to activate VESA video mode on boot. + (Deprecated) This option, if set, activates the VESA 800x600 video + mode on boot and disables kernel modesetting. It is equivalent to + specifying <literal>[ "vga=0x317" "nomodeset" ]</literal> in the + <option>boot.kernelParams</option> option. This option is + deprecated as of 2020: Xorg now works better with modesetting, and + you might want a different VESA vga setting, anyway. ''; }; @@ -187,140 +192,144 @@ in ###### implementation - config = mkIf (!config.boot.isContainer) { - - system.build = { inherit kernel; }; - - system.modulesTree = [ kernel ] ++ config.boot.extraModulePackages; - - # Implement consoleLogLevel both in early boot and using sysctl - # (so you don't need to reboot to have changes take effect). - boot.kernelParams = - [ "loglevel=${toString config.boot.consoleLogLevel}" ] ++ - optionals config.boot.vesa [ "vga=0x317" "nomodeset" ]; - - boot.kernel.sysctl."kernel.printk" = mkDefault config.boot.consoleLogLevel; - - boot.kernelModules = [ "loop" "atkbd" ]; - - boot.initrd.availableKernelModules = - [ # Note: most of these (especially the SATA/PATA modules) - # shouldn't be included by default since nixos-generate-config - # detects them, but I'm keeping them for now for backwards - # compatibility. - - # Some SATA/PATA stuff. - "ahci" - "sata_nv" - "sata_via" - "sata_sis" - "sata_uli" - "ata_piix" - "pata_marvell" - - # Standard SCSI stuff. - "sd_mod" - "sr_mod" - - # SD cards and internal eMMC drives. - "mmc_block" - - # Support USB keyboards, in case the boot fails and we only have - # a USB keyboard, or for LUKS passphrase prompt. - "uhci_hcd" - "ehci_hcd" - "ehci_pci" - "ohci_hcd" - "ohci_pci" - "xhci_hcd" - "xhci_pci" - "usbhid" - "hid_generic" "hid_lenovo" "hid_apple" "hid_roccat" - "hid_logitech_hidpp" "hid_logitech_dj" - - ] ++ optionals (pkgs.stdenv.isi686 || pkgs.stdenv.isx86_64) [ - # Misc. x86 keyboard stuff. - "pcips2" "atkbd" "i8042" - - # x86 RTC needed by the stage 2 init script. - "rtc_cmos" - ]; - - boot.initrd.kernelModules = - [ # For LVM. - "dm_mod" - ]; - - # The Linux kernel >= 2.6.27 provides firmware. - hardware.firmware = [ kernel ]; - - # Create /etc/modules-load.d/nixos.conf, which is read by - # systemd-modules-load.service to load required kernel modules. - environment.etc = singleton - { target = "modules-load.d/nixos.conf"; - source = kernelModulesConf; - }; - - systemd.services.systemd-modules-load = - { wantedBy = [ "multi-user.target" ]; - restartTriggers = [ kernelModulesConf ]; - serviceConfig = - { # Ignore failed module loads. Typically some of the - # modules in ‘boot.kernelModules’ are "nice to have but - # not required" (e.g. acpi-cpufreq), so we don't want to - # barf on those. - SuccessExitStatus = "0 1"; + config = mkMerge + [ (mkIf config.boot.initrd.enable { + boot.initrd.availableKernelModules = + [ # Note: most of these (especially the SATA/PATA modules) + # shouldn't be included by default since nixos-generate-config + # detects them, but I'm keeping them for now for backwards + # compatibility. + + # Some SATA/PATA stuff. + "ahci" + "sata_nv" + "sata_via" + "sata_sis" + "sata_uli" + "ata_piix" + "pata_marvell" + + # Standard SCSI stuff. + "sd_mod" + "sr_mod" + + # SD cards and internal eMMC drives. + "mmc_block" + + # Support USB keyboards, in case the boot fails and we only have + # a USB keyboard, or for LUKS passphrase prompt. + "uhci_hcd" + "ehci_hcd" + "ehci_pci" + "ohci_hcd" + "ohci_pci" + "xhci_hcd" + "xhci_pci" + "usbhid" + "hid_generic" "hid_lenovo" "hid_apple" "hid_roccat" + "hid_logitech_hidpp" "hid_logitech_dj" + + ] ++ optionals (pkgs.stdenv.isi686 || pkgs.stdenv.isx86_64) [ + # Misc. x86 keyboard stuff. + "pcips2" "atkbd" "i8042" + + # x86 RTC needed by the stage 2 init script. + "rtc_cmos" + ]; + + boot.initrd.kernelModules = + [ # For LVM. + "dm_mod" + ]; + }) + + (mkIf (!config.boot.isContainer) { + system.build = { inherit kernel; }; + + system.modulesTree = [ kernel ] ++ config.boot.extraModulePackages; + + # Implement consoleLogLevel both in early boot and using sysctl + # (so you don't need to reboot to have changes take effect). + boot.kernelParams = + [ "loglevel=${toString config.boot.consoleLogLevel}" ] ++ + optionals config.boot.vesa [ "vga=0x317" "nomodeset" ]; + + boot.kernel.sysctl."kernel.printk" = mkDefault config.boot.consoleLogLevel; + + boot.kernelModules = [ "loop" "atkbd" ]; + + # The Linux kernel >= 2.6.27 provides firmware. + hardware.firmware = [ kernel ]; + + # Create /etc/modules-load.d/nixos.conf, which is read by + # systemd-modules-load.service to load required kernel modules. + environment.etc = + { "modules-load.d/nixos.conf".source = kernelModulesConf; }; - }; - - lib.kernelConfig = { - isYes = option: { - assertion = config: config.isYes option; - message = "CONFIG_${option} is not yes!"; - configLine = "CONFIG_${option}=y"; - }; - - isNo = option: { - assertion = config: config.isNo option; - message = "CONFIG_${option} is not no!"; - configLine = "CONFIG_${option}=n"; - }; - - isModule = option: { - assertion = config: config.isModule option; - message = "CONFIG_${option} is not built as a module!"; - configLine = "CONFIG_${option}=m"; - }; - - ### Usually you will just want to use these two - # True if yes or module - isEnabled = option: { - assertion = config: config.isEnabled option; - message = "CONFIG_${option} is not enabled!"; - configLine = "CONFIG_${option}=y"; - }; - - # True if no or omitted - isDisabled = option: { - assertion = config: config.isDisabled option; - message = "CONFIG_${option} is not disabled!"; - configLine = "CONFIG_${option}=n"; - }; - }; - # The config options that all modules can depend upon - system.requiredKernelConfig = with config.lib.kernelConfig; [ - # !!! Should this really be needed? - (isYes "MODULES") - (isYes "BINFMT_ELF") - ] ++ (optional (randstructSeed != "") (isYes "GCC_PLUGIN_RANDSTRUCT")); + systemd.services.systemd-modules-load = + { wantedBy = [ "multi-user.target" ]; + restartTriggers = [ kernelModulesConf ]; + serviceConfig = + { # Ignore failed module loads. Typically some of the + # modules in ‘boot.kernelModules’ are "nice to have but + # not required" (e.g. acpi-cpufreq), so we don't want to + # barf on those. + SuccessExitStatus = "0 1"; + }; + }; - # nixpkgs kernels are assumed to have all required features - assertions = if config.boot.kernelPackages.kernel ? features then [] else - let cfg = config.boot.kernelPackages.kernel.config; in map (attrs: - { assertion = attrs.assertion cfg; inherit (attrs) message; } - ) config.system.requiredKernelConfig; + lib.kernelConfig = { + isYes = option: { + assertion = config: config.isYes option; + message = "CONFIG_${option} is not yes!"; + configLine = "CONFIG_${option}=y"; + }; - }; + isNo = option: { + assertion = config: config.isNo option; + message = "CONFIG_${option} is not no!"; + configLine = "CONFIG_${option}=n"; + }; + + isModule = option: { + assertion = config: config.isModule option; + message = "CONFIG_${option} is not built as a module!"; + configLine = "CONFIG_${option}=m"; + }; + + ### Usually you will just want to use these two + # True if yes or module + isEnabled = option: { + assertion = config: config.isEnabled option; + message = "CONFIG_${option} is not enabled!"; + configLine = "CONFIG_${option}=y"; + }; + + # True if no or omitted + isDisabled = option: { + assertion = config: config.isDisabled option; + message = "CONFIG_${option} is not disabled!"; + configLine = "CONFIG_${option}=n"; + }; + }; + + # The config options that all modules can depend upon + system.requiredKernelConfig = with config.lib.kernelConfig; + [ + # !!! Should this really be needed? + (isYes "MODULES") + (isYes "BINFMT_ELF") + ] ++ (optional (randstructSeed != "") (isYes "GCC_PLUGIN_RANDSTRUCT")); + + # nixpkgs kernels are assumed to have all required features + assertions = if config.boot.kernelPackages.kernel ? features then [] else + let cfg = config.boot.kernelPackages.kernel.config; in map (attrs: + { assertion = attrs.assertion cfg; inherit (attrs) message; } + ) config.system.requiredKernelConfig; + + }) + + ]; } diff --git a/nixos/modules/system/boot/loader/grub/grub.nix b/nixos/modules/system/boot/loader/grub/grub.nix index 9a4db84f7b7..c775632a4aa 100644 --- a/nixos/modules/system/boot/loader/grub/grub.nix +++ b/nixos/modules/system/boot/loader/grub/grub.nix @@ -224,7 +224,11 @@ in extraConfig = mkOption { default = ""; - example = "serial; terminal_output.serial"; + example = '' + serial --unit=0 --speed=115200 --word=8 --parity=no --stop=1 + terminal_input --append serial + terminal_output --append serial + ''; type = types.lines; description = '' Additional GRUB commands inserted in the configuration file @@ -584,7 +588,7 @@ in { boot.loader.grub.splashImage = mkDefault ( if cfg.version == 1 then pkgs.fetchurl { - url = http://www.gnome-look.org/CONTENT/content-files/36909-soft-tux.xpm.gz; + url = "http://www.gnome-look.org/CONTENT/content-files/36909-soft-tux.xpm.gz"; sha256 = "14kqdx2lfqvh40h6fjjzqgff1mwk74dmbjvmqphi6azzra7z8d59"; } # GRUB 1.97 doesn't support gzipped XPMs. @@ -630,7 +634,7 @@ in boot.loader.grub.extraPrepareConfig = concatStrings (mapAttrsToList (n: v: '' - ${pkgs.coreutils}/bin/cp -pf "${v}" "/boot/${n}" + ${pkgs.coreutils}/bin/cp -pf "${v}" "@bootPath@/${n}" '') config.boot.loader.grub.extraFiles); assertions = [ diff --git a/nixos/modules/system/boot/loader/grub/install-grub.pl b/nixos/modules/system/boot/loader/grub/install-grub.pl index a09c5dc4761..8df18cbd901 100644 --- a/nixos/modules/system/boot/loader/grub/install-grub.pl +++ b/nixos/modules/system/boot/loader/grub/install-grub.pl @@ -409,7 +409,7 @@ $conf .= "$extraEntries\n" unless $extraEntriesBeforeNixOS; # Find all the children of the current default configuration # Do not search for grand children -my @links = sort (glob "$defaultConfig/fine-tune/*"); +my @links = sort (glob "$defaultConfig/specialisation/*"); foreach my $link (@links) { my $entryName = ""; @@ -425,7 +425,8 @@ foreach my $link (@links) { if ($cfgName) { $entryName = $cfgName; } else { - $entryName = "($date - $version)"; + my $linkname = basename($link); + $entryName = "($linkname - $date - $version)"; } addEntry("NixOS - $entryName", $link); } @@ -475,6 +476,9 @@ if ($grubVersion == 2) { } } +# extraPrepareConfig could refer to @bootPath@, which we have to substitute +$extraPrepareConfig =~ s/\@bootPath\@/$bootPath/g; + # Run extraPrepareConfig in sh if ($extraPrepareConfig ne "") { system((get("shell"), "-c", $extraPrepareConfig)); diff --git a/nixos/modules/system/boot/loader/grub/memtest.nix b/nixos/modules/system/boot/loader/grub/memtest.nix index 94e5a14174b..71e50dd0577 100644 --- a/nixos/modules/system/boot/loader/grub/memtest.nix +++ b/nixos/modules/system/boot/loader/grub/memtest.nix @@ -1,4 +1,4 @@ -# This module adds Memtest86+ to the GRUB boot menu. +# This module adds Memtest86+/Memtest86 to the GRUB boot menu. { config, lib, pkgs, ... }: @@ -6,6 +6,7 @@ with lib; let memtest86 = pkgs.memtest86plus; + efiSupport = config.boot.loader.grub.efiSupport; cfg = config.boot.loader.grub.memtest86; in @@ -18,8 +19,11 @@ in default = false; type = types.bool; description = '' - Make Memtest86+, a memory testing program, available from the - GRUB boot menu. + Make Memtest86+ (or MemTest86 if EFI support is enabled), + a memory testing program, available from the + GRUB boot menu. MemTest86 is an unfree program, so + this requires <literal>allowUnfree</literal> to be set to + <literal>true</literal>. ''; }; @@ -75,19 +79,38 @@ in }; }; - config = mkIf cfg.enable { - - boot.loader.grub.extraEntries = - if config.boot.loader.grub.version == 2 then - '' - menuentry "Memtest86+" { - linux16 @bootRoot@/memtest.bin ${toString cfg.params} - } - '' - else - throw "Memtest86+ is not supported with GRUB 1."; - - boot.loader.grub.extraFiles."memtest.bin" = "${memtest86}/memtest.bin"; + config = mkMerge [ + (mkIf (cfg.enable && efiSupport) { + assertions = [ + { + assertion = cfg.params == []; + message = "Parameters are not available for MemTest86"; + } + ]; + + boot.loader.grub.extraFiles = { + "memtest86.efi" = "${pkgs.memtest86-efi}/BOOTX64.efi"; + }; - }; + boot.loader.grub.extraEntries = '' + menuentry "Memtest86" { + chainloader /memtest86.efi + } + ''; + }) + + (mkIf (cfg.enable && !efiSupport) { + boot.loader.grub.extraEntries = + if config.boot.loader.grub.version == 2 then + '' + menuentry "Memtest86+" { + linux16 @bootRoot@/memtest.bin ${toString cfg.params} + } + '' + else + throw "Memtest86+ is not supported with GRUB 1."; + + boot.loader.grub.extraFiles."memtest.bin" = "${memtest86}/memtest.bin"; + }) + ]; } diff --git a/nixos/modules/system/boot/loader/init-script/init-script-builder.sh b/nixos/modules/system/boot/loader/init-script/init-script-builder.sh index 08d4ab14c9c..6f48d2539ac 100644 --- a/nixos/modules/system/boot/loader/init-script/init-script-builder.sh +++ b/nixos/modules/system/boot/loader/init-script/init-script-builder.sh @@ -69,7 +69,7 @@ addEntry "NixOS - Default" $defaultConfig "" # Add all generations of the system profile to the menu, in reverse # (most recent to least recent) order. -for link in $((ls -d $defaultConfig/fine-tune/* ) | sort -n); do +for link in $((ls -d $defaultConfig/specialisation/* ) | sort -n); do date=$(stat --printf="%y\n" $link | sed 's/\..*//') addEntry "NixOS - variation" $link "" done diff --git a/nixos/modules/system/boot/loader/loader.nix b/nixos/modules/system/boot/loader/loader.nix index 7fbda9ef0f5..01475f79b9c 100644 --- a/nixos/modules/system/boot/loader/loader.nix +++ b/nixos/modules/system/boot/loader/loader.nix @@ -3,6 +3,11 @@ with lib; { + imports = [ + (mkRenamedOptionModule [ "boot" "loader" "grub" "timeout" ] [ "boot" "loader" "timeout" ]) + (mkRenamedOptionModule [ "boot" "loader" "gummiboot" "timeout" ] [ "boot" "loader" "timeout" ]) + ]; + options = { boot.loader.timeout = mkOption { default = 5; @@ -12,4 +17,4 @@ with lib; ''; }; }; -} \ No newline at end of file +} diff --git a/nixos/modules/system/boot/luksroot.nix b/nixos/modules/system/boot/luksroot.nix index a4029d766b0..31f1e22cda3 100644 --- a/nixos/modules/system/boot/luksroot.nix +++ b/nixos/modules/system/boot/luksroot.nix @@ -4,6 +4,7 @@ with lib; let luks = config.boot.initrd.luks; + kernelPackages = config.boot.kernelPackages; commonFunctions = '' die() { @@ -126,7 +127,7 @@ let gpg-agent --daemon --scdaemon-program $out/bin/scdaemon > /dev/null 2> /dev/null ''} - + # Disable all input echo for the whole stage. We could use read -s # instead but that would ocasionally leak characters between read # invocations. @@ -139,7 +140,7 @@ let umount /crypt-ramfs 2>/dev/null ''; - openCommand = name': { name, device, header, keyFile, keyFileSize, keyFileOffset, allowDiscards, yubikey, gpgCard, fallbackToPassword, ... }: assert name' == name; + openCommand = name': { name, device, header, keyFile, keyFileSize, keyFileOffset, allowDiscards, yubikey, gpgCard, fido2, fallbackToPassword, ... }: assert name' == name; let csopen = "cryptsetup luksOpen ${device} ${name} ${optionalString allowDiscards "--allow-discards"} ${optionalString (header != null) "--header=${header}"}"; cschange = "cryptsetup luksChangeKey ${device} ${optionalString (header != null) "--header=${header}"}"; @@ -387,7 +388,31 @@ let } ''} - ${if (luks.yubikeySupport && (yubikey != null)) || (luks.gpgSupport && (gpgCard != null)) then '' + ${optionalString (luks.fido2Support && (fido2.credential != null)) '' + + open_with_hardware() { + local passsphrase + + ${if fido2.passwordLess then '' + export passphrase="" + '' else '' + read -rsp "FIDO2 salt for ${device}: " passphrase + echo + ''} + ${optionalString (lib.versionOlder kernelPackages.kernel.version "5.4") '' + echo "On systems with Linux Kernel < 5.4, it might take a while to initialize the CRNG, you might want to use linuxPackages_latest." + echo "Please move your mouse to create needed randomness." + ''} + echo "Waiting for your FIDO2 device..." + fido2luks -i open ${device} ${name} ${fido2.credential} --await-dev ${toString fido2.gracePeriod} --salt string:$passphrase + if [ $? -ne 0 ]; then + echo "No FIDO2 key found, falling back to normal open procedure" + open_normally + fi + } + ''} + + ${if (luks.yubikeySupport && (yubikey != null)) || (luks.gpgSupport && (gpgCard != null)) || (luks.fido2Support && (fido2.credential != null)) then '' open_with_hardware '' else '' open_normally @@ -417,6 +442,9 @@ let in { + imports = [ + (mkRemovedOptionModule [ "boot" "initrd" "luks" "enable" ] "") + ]; options = { @@ -605,6 +633,31 @@ in }); }; + fido2 = { + credential = mkOption { + default = null; + example = "f1d00200d8dc783f7fb1e10ace8da27f8312d72692abfca2f7e4960a73f48e82e1f7571f6ebfcee9fb434f9886ccc8fcc52a6614d8d2"; + type = types.str; + description = "The FIDO2 credential ID."; + }; + + gracePeriod = mkOption { + default = 10; + type = types.int; + description = "Time in seconds to wait for the FIDO2 key."; + }; + + passwordLess = mkOption { + default = false; + type = types.bool; + description = '' + Defines whatever to use an empty string as a default salt. + + Enable only when your device is PIN protected, such as <link xlink:href="https://trezor.io/">Trezor</link>. + ''; + }; + }; + yubikey = mkOption { default = null; description = '' @@ -703,6 +756,15 @@ in and a Yubikey to work with this feature. ''; }; + + boot.initrd.luks.fido2Support = mkOption { + default = false; + type = types.bool; + description = '' + Enables support for authenticating with FIDO2 devices. + ''; + }; + }; config = mkIf (luks.devices != {} || luks.forceLuksSupportInInitrd) { @@ -711,6 +773,14 @@ in [ { assertion = !(luks.gpgSupport && luks.yubikeySupport); message = "Yubikey and GPG Card may not be used at the same time."; } + + { assertion = !(luks.gpgSupport && luks.fido2Support); + message = "FIDO2 and GPG Card may not be used at the same time."; + } + + { assertion = !(luks.fido2Support && luks.yubikeySupport); + message = "FIDO2 and Yubikey may not be used at the same time."; + } ]; # actually, sbp2 driver is the one enabling the DMA attack, but this needs to be tested @@ -750,6 +820,11 @@ in chmod +x $out/bin/openssl-wrap ''} + ${optionalString luks.fido2Support '' + copy_bin_and_libs ${pkgs.fido2luks}/bin/fido2luks + ''} + + ${optionalString luks.gpgSupport '' copy_bin_and_libs ${pkgs.gnupg}/bin/gpg copy_bin_and_libs ${pkgs.gnupg}/bin/gpg-agent @@ -780,6 +855,9 @@ in $out/bin/gpg-agent --version $out/bin/scdaemon --version ''} + ${optionalString luks.fido2Support '' + $out/bin/fido2luks --version + ''} ''; boot.initrd.preFailCommands = postCommands; diff --git a/nixos/modules/system/boot/networkd.nix b/nixos/modules/system/boot/networkd.nix index 226769f1059..a7580fb1997 100644 --- a/nixos/modules/system/boot/networkd.nix +++ b/nixos/modules/system/boot/networkd.nix @@ -11,7 +11,7 @@ let checkLink = checkUnitConfig "Link" [ (assertOnlyFields [ "Description" "Alias" "MACAddressPolicy" "MACAddress" "NamePolicy" "Name" "OriginalName" - "MTUBytes" "BitsPerSecond" "Duplex" "AutoNegotiation" "WakeOnLan" "Port" + "MTUBytes" "BitsPerSecond" "Duplex" "AutoNegotiation" "WakeOnLan" "Port" "Advertise" "TCPSegmentationOffload" "TCP6SegmentationOffload" "GenericSegmentationOffload" "GenericReceiveOffload" "LargeReceiveOffload" "RxChannels" "TxChannels" "OtherChannels" "CombinedChannels" @@ -49,12 +49,17 @@ let (assertValueOneOf "Kind" [ "bond" "bridge" "dummy" "gre" "gretap" "ip6gre" "ip6tnl" "ip6gretap" "ipip" "ipvlan" "macvlan" "macvtap" "sit" "tap" "tun" "veth" "vlan" "vti" "vti6" - "vxlan" "geneve" "vrf" "vcan" "vxcan" "wireguard" "netdevsim" + "vxlan" "geneve" "vrf" "vcan" "vxcan" "wireguard" "netdevsim" "xfrm" ]) (assertByteFormat "MTUBytes") (assertMacAddress "MACAddress") ]; + checkVRF = checkUnitConfig "VRF" [ + (assertOnlyFields [ "Table" ]) + (assertMinimum "Table" 0) + ]; + # NOTE The PrivateKey directive is missing on purpose here, please # do not add it to this list. The nix store is world-readable let's # refrain ourselves from providing a footgun. @@ -62,7 +67,12 @@ let (assertOnlyFields [ "PrivateKeyFile" "ListenPort" "FwMark" ]) - (assertRange "FwMark" 1 4294967295) + # The following check won't work on nix <= 2.2 + # see https://github.com/NixOS/nix/pull/2378 + # + # Add this again when we'll have drop the + # nix < 2.2 support. + # (assertRange "FwMark" 1 4294967295) ]; # NOTE The PresharedKey directive is missing on purpose here, please @@ -172,6 +182,19 @@ let (assertValueOneOf "AllSlavesActive" boolValues) ]; + checkXfrm = checkUnitConfig "Xfrm" [ + (assertOnlyFields [ + "InterfaceId" "Independent" + ]) + # The following check won't work on nix <= 2.2 + # see https://github.com/NixOS/nix/pull/2378 + # + # Add this again when we'll have drop the + # nix < 2.2 support. + # (assertRange "InterfaceId" 1 4294967295) + (assertValueOneOf "Independent" boolValues) + ]; + checkNetwork = checkUnitConfig "Network" [ (assertOnlyFields [ "Description" "DHCP" "DHCPServer" "LinkLocalAddressing" "IPv4LLRoute" @@ -182,7 +205,7 @@ let "IPv6HopLimit" "IPv4ProxyARP" "IPv6ProxyNDP" "IPv6ProxyNDPAddress" "IPv6PrefixDelegation" "IPv6MTUBytes" "Bridge" "Bond" "VRF" "VLAN" "IPVLAN" "MACVLAN" "VXLAN" "Tunnel" "ActiveSlave" "PrimarySlave" - "ConfigureWithoutCarrier" + "ConfigureWithoutCarrier" "Xfrm" ]) # Note: For DHCP the values both, none, v4, v6 are deprecated (assertValueOneOf "DHCP" ["yes" "no" "ipv4" "ipv6" "both" "none" "v4" "v6"]) @@ -222,6 +245,26 @@ let (assertValueOneOf "AutoJoin" boolValues) ]; + checkRoutingPolicyRule = checkUnitConfig "RoutingPolicyRule" [ + (assertOnlyFields [ + "TypeOfService" "From" "To" "FirewallMark" "Table" "Priority" + "IncomingInterface" "OutgoingInterface" "SourcePort" "DestinationPort" + "IPProtocol" "InvertRule" "Family" + ]) + (assertRange "TypeOfService" 0 255) + # The following check won't work on nix <= 2.2 + # see https://github.com/NixOS/nix/pull/2378 + # + # Add this again when we'll have drop the + # nix < 2.2 support. + # (assertRange "FirewallMark" 1 4294967295) + (assertInt "Priority") + (assertPort "SourcePort") + (assertPort "DestinationPort") + (assertValueOneOf "InvertRule" boolValues) + (assertValueOneOf "Family" ["ipv4" "ipv6" "both"]) + ]; + checkRoute = checkUnitConfig "Route" [ (assertOnlyFields [ "Gateway" "GatewayOnLink" "Destination" "Source" "Metric" @@ -276,7 +319,7 @@ let (assertValueOneOf "ARP" boolValues) (assertValueOneOf "Multicast" boolValues) (assertValueOneOf "Unmanaged" boolValues) - (assertValueOneOf "RequiredForOnline" boolValues) + (assertValueOneOf "RequiredForOnline" (boolValues ++ ["off" "no-carrier" "dormant" "degraded-carrier" "carrier" "degraded" "enslaved" "routable"])) ]; @@ -312,6 +355,14 @@ let }; linkOptions = commonNetworkOptions // { + # overwrite enable option from above + enable = mkOption { + default = true; + type = types.bool; + description = '' + Whether to enable this .link unit. It's handled by udev no matter if <command>systemd-networkd</command> is enabled or not + ''; + }; linkConfig = mkOption { default = {}; @@ -341,6 +392,21 @@ let ''; }; + vrfConfig = mkOption { + default = {}; + example = { Table = 2342; }; + type = types.addCheck (types.attrsOf unitOption) checkVRF; + description = '' + Each attribute in this set specifies an option in the + <literal>[VRF]</literal> section of the unit. See + <citerefentry><refentrytitle>systemd.netdev</refentrytitle> + <manvolnum>5</manvolnum></citerefentry> for details. + A detailed explanation about how VRFs work can be found in the + <link xlink:href="https://www.kernel.org/doc/Documentation/networking/vrf.txt">kernel + docs</link>. + ''; + }; + wireguardConfig = mkOption { default = {}; example = { @@ -477,6 +543,18 @@ let ''; }; + xfrmConfig = mkOption { + default = {}; + example = { InterfaceId = 1; }; + type = types.addCheck (types.attrsOf unitOption) checkXfrm; + description = '' + Each attribute in this set specifies an option in the + <literal>[Xfrm]</literal> section of the unit. See + <citerefentry><refentrytitle>systemd.netdev</refentrytitle> + <manvolnum>5</manvolnum></citerefentry> for details. + ''; + }; + }; addressOptions = { @@ -495,6 +573,22 @@ let }; }; + routingPolicyRulesOptions = { + options = { + routingPolicyRuleConfig = mkOption { + default = { }; + example = { routingPolicyRuleConfig = { Table = 10; IncomingInterface = "eth1"; Family = "both"; } ;}; + type = types.addCheck (types.attrsOf unitOption) checkRoutingPolicyRule; + description = '' + Each attribute in this set specifies an option in the + <literal>[RoutingPolicyRule]</literal> section of the unit. See + <citerefentry><refentrytitle>systemd.network</refentrytitle> + <manvolnum>5</manvolnum></citerefentry> for details. + ''; + }; + }; + }; + routeOptions = { options = { routeConfig = mkOption { @@ -712,6 +806,16 @@ let ''; }; + xfrm = mkOption { + default = [ ]; + type = types.listOf types.str; + description = '' + A list of xfrm interfaces to be added to the network section of the + unit. See <citerefentry><refentrytitle>systemd.network</refentrytitle> + <manvolnum>5</manvolnum></citerefentry> for details. + ''; + }; + addresses = mkOption { default = [ ]; type = with types; listOf (submodule addressOptions); @@ -722,6 +826,16 @@ let ''; }; + routingPolicyRules = mkOption { + default = [ ]; + type = with types; listOf (submodule routingPolicyRulesOptions); + description = '' + A list of routing policy rules sections to be added to the unit. See + <citerefentry><refentrytitle>systemd.network</refentrytitle> + <manvolnum>5</manvolnum></citerefentry> for details. + ''; + }; + routes = mkOption { default = [ ]; type = with types; listOf (submodule routeOptions); @@ -810,6 +924,16 @@ let ${attrsToSection def.bondConfig} ''} + ${optionalString (def.xfrmConfig != { }) '' + [Xfrm] + ${attrsToSection def.xfrmConfig} + + ''} + ${optionalString (def.vrfConfig != { }) '' + [VRF] + ${attrsToSection def.vrfConfig} + + ''} ${optionalString (def.wireguardConfig != { }) '' [WireGuard] ${attrsToSection def.wireguardConfig} @@ -847,6 +971,7 @@ let ${concatStringsSep "\n" (map (s: "MACVLAN=${s}") def.macvlan)} ${concatStringsSep "\n" (map (s: "VXLAN=${s}") def.vxlan)} ${concatStringsSep "\n" (map (s: "Tunnel=${s}") def.tunnel)} + ${concatStringsSep "\n" (map (s: "Xfrm=${s}") def.xfrm)} ${optionalString (def.dhcpConfig != { }) '' [DHCP] @@ -868,14 +993,19 @@ let ${attrsToSection x.routeConfig} '')} + ${flip concatMapStrings def.routingPolicyRules (x: '' + [RoutingPolicyRule] + ${attrsToSection x.routingPolicyRuleConfig} + + '')} ${def.extraConfig} ''; }; - unitFiles = map (name: { - target = "systemd/network/${name}"; - source = "${cfg.units.${name}.unit}/${name}"; - }) (attrNames cfg.units); + unitFiles = listToAttrs (map (name: { + name = "systemd/network/${name}"; + value.source = "${cfg.units.${name}.unit}/${name}"; + }) (attrNames cfg.units)); in { @@ -911,9 +1041,10 @@ in systemd.network.units = mkOption { description = "Definition of networkd units."; default = {}; + internal = true; type = with types; attrsOf (submodule ( { name, config, ... }: - { options = concreteUnitOptions; + { options = mapAttrs (_: x: x // { internal = true; }) concreteUnitOptions; config = { unit = mkDefault (makeUnit name config); }; @@ -922,44 +1053,49 @@ in }; - config = mkIf config.systemd.network.enable { + config = mkMerge [ + # .link units are honored by udev, no matter if systemd-networkd is enabled or not. + { + systemd.network.units = mapAttrs' (n: v: nameValuePair "${n}.link" (linkToUnit n v)) cfg.links; + environment.etc = unitFiles; + } - users.users.systemd-network.group = "systemd-network"; + (mkIf config.systemd.network.enable { - systemd.additionalUpstreamSystemUnits = [ - "systemd-networkd.service" "systemd-networkd-wait-online.service" - ]; + users.users.systemd-network.group = "systemd-network"; - systemd.network.units = mapAttrs' (n: v: nameValuePair "${n}.link" (linkToUnit n v)) cfg.links - // mapAttrs' (n: v: nameValuePair "${n}.netdev" (netdevToUnit n v)) cfg.netdevs - // mapAttrs' (n: v: nameValuePair "${n}.network" (networkToUnit n v)) cfg.networks; + systemd.additionalUpstreamSystemUnits = [ + "systemd-networkd.service" "systemd-networkd-wait-online.service" + ]; - environment.etc = unitFiles; + systemd.network.units = mapAttrs' (n: v: nameValuePair "${n}.netdev" (netdevToUnit n v)) cfg.netdevs + // mapAttrs' (n: v: nameValuePair "${n}.network" (networkToUnit n v)) cfg.networks; - systemd.services.systemd-networkd = { - wantedBy = [ "multi-user.target" ]; - restartTriggers = map (f: f.source) (unitFiles); - # prevent race condition with interface renaming (#39069) - requires = [ "systemd-udev-settle.service" ]; - after = [ "systemd-udev-settle.service" ]; - }; + systemd.services.systemd-networkd = { + wantedBy = [ "multi-user.target" ]; + restartTriggers = map (x: x.source) (attrValues unitFiles); + # prevent race condition with interface renaming (#39069) + requires = [ "systemd-udev-settle.service" ]; + after = [ "systemd-udev-settle.service" ]; + }; - systemd.services.systemd-networkd-wait-online = { - wantedBy = [ "network-online.target" ]; - }; + systemd.services.systemd-networkd-wait-online = { + wantedBy = [ "network-online.target" ]; + }; - systemd.services."systemd-network-wait-online@" = { - description = "Wait for Network Interface %I to be Configured"; - conflicts = [ "shutdown.target" ]; - requisite = [ "systemd-networkd.service" ]; - after = [ "systemd-networkd.service" ]; - serviceConfig = { - Type = "oneshot"; - RemainAfterExit = true; - ExecStart = "${config.systemd.package}/lib/systemd/systemd-networkd-wait-online -i %I"; + systemd.services."systemd-network-wait-online@" = { + description = "Wait for Network Interface %I to be Configured"; + conflicts = [ "shutdown.target" ]; + requisite = [ "systemd-networkd.service" ]; + after = [ "systemd-networkd.service" ]; + serviceConfig = { + Type = "oneshot"; + RemainAfterExit = true; + ExecStart = "${config.systemd.package}/lib/systemd/systemd-networkd-wait-online -i %I"; + }; }; - }; - services.resolved.enable = mkDefault true; - }; + services.resolved.enable = mkDefault true; + }) + ]; } diff --git a/nixos/modules/system/boot/stage-1-init.sh b/nixos/modules/system/boot/stage-1-init.sh index f520bf54ad1..607aec87f01 100644 --- a/nixos/modules/system/boot/stage-1-init.sh +++ b/nixos/modules/system/boot/stage-1-init.sh @@ -210,6 +210,8 @@ done # Create device nodes in /dev. @preDeviceCommands@ echo "running udev..." +mkdir -p /etc/systemd +ln -sfn @linkUnits@ /etc/systemd/network mkdir -p /etc/udev ln -sfn @udevRules@ /etc/udev/rules.d mkdir -p /dev/.mdadm @@ -266,7 +268,7 @@ checkFS() { return 0 fi - # Device might be already mounted manually + # Device might be already mounted manually # e.g. NBD-device or the host filesystem of the file which contains encrypted root fs if mount | grep -q "^$device on "; then echo "skip checking already mounted $device" @@ -334,8 +336,10 @@ mountFS() { # Filter out x- options, which busybox doesn't do yet. local optionsFiltered="$(IFS=,; for i in $options; do if [ "${i:0:2}" != "x-" ]; then echo -n $i,; fi; done)" + # Prefix (lower|upper|work)dir with /mnt-root (overlayfs) + local optionsPrefixed="$( echo "$optionsFiltered" | sed -E 's#\<(lowerdir|upperdir|workdir)=#\1=/mnt-root#g' )" - echo "$device /mnt-root$mountPoint $fsType $optionsFiltered" >> /etc/fstab + echo "$device /mnt-root$mountPoint $fsType $optionsPrefixed" >> /etc/fstab checkFS "$device" "$fsType" @@ -349,15 +353,16 @@ mountFS() { elif [ "$fsType" = f2fs ]; then echo "resizing $device..." fsck.f2fs -fp "$device" - resize.f2fs "$device" + resize.f2fs "$device" fi ;; esac - # Create backing directories for unionfs-fuse. - if [ "$fsType" = unionfs-fuse ]; then - for i in $(IFS=:; echo ${options##*,dirs=}); do - mkdir -m 0700 -p /mnt-root"${i%=*}" + # Create backing directories for overlayfs + if [ "$fsType" = overlay ]; then + for i in upper work; do + dir="$( echo "$optionsPrefixed" | grep -o "${i}dir=[^,]*" )" + mkdir -m 0700 -p "${dir##*=}" done fi diff --git a/nixos/modules/system/boot/stage-1.nix b/nixos/modules/system/boot/stage-1.nix index 4c2d130d5a5..dfd158e2d75 100644 --- a/nixos/modules/system/boot/stage-1.nix +++ b/nixos/modules/system/boot/stage-1.nix @@ -120,6 +120,7 @@ let # Copy udev. copy_bin_and_libs ${udev}/lib/systemd/systemd-udevd + copy_bin_and_libs ${udev}/lib/systemd/systemd-sysctl copy_bin_and_libs ${udev}/bin/udevadm for BIN in ${udev}/lib/udev/*_id; do copy_bin_and_libs $BIN @@ -136,12 +137,17 @@ let ''} # Copy secrets if needed. + # + # TODO: move out to a separate script; see #85000. ${optionalString (!config.boot.loader.supportsInitrdSecrets) (concatStringsSep "\n" (mapAttrsToList (dest: source: let source' = if source == null then dest else source; in '' mkdir -p $(dirname "$out/secrets/${dest}") - cp -a ${source'} "$out/secrets/${dest}" + # Some programs (e.g. ssh) doesn't like secrets to be + # symlinks, so we use `cp -L` here to match the + # behaviour when secrets are natively supported. + cp -Lr ${source'} "$out/secrets/${dest}" '' ) config.boot.initrd.secrets)) } @@ -198,6 +204,14 @@ let ''; # */ + linkUnits = pkgs.runCommand "link-units" { + allowedReferences = [ extraUtils ]; + preferLocalBuild = true; + } '' + mkdir -p $out + cp -v ${udev}/lib/systemd/network/*.link $out/ + ''; + udevRules = pkgs.runCommand "udev-rules" { allowedReferences = [ extraUtils ]; preferLocalBuild = true; @@ -208,7 +222,9 @@ let cp -v ${udev}/lib/udev/rules.d/60-cdrom_id.rules $out/ cp -v ${udev}/lib/udev/rules.d/60-persistent-storage.rules $out/ + cp -v ${udev}/lib/udev/rules.d/75-net-description.rules $out/ cp -v ${udev}/lib/udev/rules.d/80-drivers.rules $out/ + cp -v ${udev}/lib/udev/rules.d/80-net-setup-link.rules $out/ cp -v ${pkgs.lvm2}/lib/udev/rules.d/*.rules $out/ ${config.boot.initrd.extraUdevRulesCommands} @@ -222,7 +238,7 @@ let --replace ${pkgs.lvm2}/sbin ${extraUtils}/bin \ --replace ${pkgs.mdadm}/sbin ${extraUtils}/sbin \ --replace ${pkgs.bash}/bin/sh ${extraUtils}/bin/sh \ - --replace ${udev}/bin/udevadm ${extraUtils}/bin/udevadm + --replace ${udev} ${extraUtils} done # Work around a bug in QEMU, which doesn't implement the "READ @@ -257,7 +273,7 @@ let ${pkgs.buildPackages.busybox}/bin/ash -n $target ''; - inherit udevRules extraUtils modulesClosure; + inherit linkUnits udevRules extraUtils modulesClosure; inherit (config.boot) resumeDevice; @@ -379,6 +395,17 @@ in ''; }; + boot.initrd.enable = mkOption { + type = types.bool; + default = !config.boot.isContainer; + defaultText = "!config.boot.isContainer"; + description = '' + Whether to enable the NixOS initial RAM disk (initrd). This may be + needed to perform some initialisation tasks (like mounting + network/encrypted file systems) before continuing the boot process. + ''; + }; + boot.initrd.prepend = mkOption { default = [ ]; type = types.listOf types.str; @@ -544,7 +571,7 @@ in }; - config = mkIf (!config.boot.isContainer) { + config = mkIf config.boot.initrd.enable { assertions = [ { assertion = any (fs: fs.mountPoint == "/") fileSystems; message = "The ‘fileSystems’ option does not specify your root file system."; @@ -554,6 +581,25 @@ in message = "boot.resumeDevice has to be an absolute path." + " Old \"x:y\" style is no longer supported."; } + # TODO: remove when #85000 is fixed + { assertion = !config.boot.loader.supportsInitrdSecrets -> + all (source: + builtins.isPath source || + (builtins.isString source && hasPrefix source builtins.storeDir)) + (attrValues config.boot.initrd.secrets); + message = '' + boot.loader.initrd.secrets values must be unquoted paths when + using a bootloader that doesn't natively support initrd + secrets, e.g.: + + boot.initrd.secrets = { + "/etc/secret" = /path/to/secret; + }; + + Note that this will result in all secrets being stored + world-readable in the Nix store! + ''; + } ]; system.build = diff --git a/nixos/modules/system/boot/systemd-lib.nix b/nixos/modules/system/boot/systemd-lib.nix index 28ad4f121bb..fa109394fed 100644 --- a/nixos/modules/system/boot/systemd-lib.nix +++ b/nixos/modules/system/boot/systemd-lib.nix @@ -59,6 +59,11 @@ in rec { optional (attr ? ${name} && ! isMacAddress attr.${name}) "Systemd ${group} field `${name}' must be a valid mac address."; + isPort = i: i >= 0 && i <= 65535; + + assertPort = name: group: attr: + optional (attr ? ${name} && ! isPort attr.${name}) + "Error on the systemd ${group} field `${name}': ${attr.name} is not a valid port number."; assertValueOneOf = name: values: group: attr: optional (attr ? ${name} && !elem attr.${name} values) @@ -109,7 +114,9 @@ in rec { (if isList value then value else [value])) as)); - generateUnits = type: units: upstreamUnits: upstreamWants: + generateUnits = generateUnits' true; + + generateUnits' = allowCollisions: type: units: upstreamUnits: upstreamWants: pkgs.runCommand "${type}-units" { preferLocalBuild = true; allowSubstitutes = false; @@ -147,7 +154,13 @@ in rec { done # Symlink all units provided listed in systemd.packages. - for i in ${toString cfg.packages}; do + packages="${toString cfg.packages}" + + # Filter duplicate directories + declare -A unique_packages + for k in $packages ; do unique_packages[$k]=1 ; done + + for i in ''${!unique_packages[@]}; do for fn in $i/etc/systemd/${type}/* $i/lib/systemd/${type}/*; do if ! [[ "$fn" =~ .wants$ ]]; then if [[ -d "$fn" ]]; then @@ -171,8 +184,13 @@ in rec { if [ "$(readlink -f $i/$fn)" = /dev/null ]; then ln -sfn /dev/null $out/$fn else - mkdir -p $out/$fn.d - ln -s $i/$fn $out/$fn.d/overrides.conf + ${if allowCollisions then '' + mkdir -p $out/$fn.d + ln -s $i/$fn $out/$fn.d/overrides.conf + '' else '' + echo "Found multiple derivations configuring $fn!" + exit 1 + ''} fi else ln -fs $i/$fn $out/ diff --git a/nixos/modules/system/boot/systemd-nspawn.nix b/nixos/modules/system/boot/systemd-nspawn.nix index 3ddd45b1348..06ea5ee49f7 100644 --- a/nixos/modules/system/boot/systemd-nspawn.nix +++ b/nixos/modules/system/boot/systemd-nspawn.nix @@ -116,7 +116,7 @@ in { in mkMerge [ (mkIf (cfg != {}) { - environment.etc."systemd/nspawn".source = mkIf (cfg != {}) (generateUnits "nspawn" units [] []); + environment.etc."systemd/nspawn".source = mkIf (cfg != {}) (generateUnits' false "nspawn" units [] []); }) { systemd.targets.multi-user.wants = [ "machines.target" ]; @@ -126,7 +126,7 @@ in { systemd.services."systemd-nspawn@".serviceConfig.ExecStart = [ "" # deliberately empty. signals systemd to override the ExecStart # Only difference between upstream is that we do not pass the -U flag - "${pkgs.systemd}/bin/systemd-nspawn --quiet --keep-unit --boot --link-journal=try-guest --network-veth --settings=override --machine=%i" + "${config.systemd.package}/bin/systemd-nspawn --quiet --keep-unit --boot --link-journal=try-guest --network-veth --settings=override --machine=%i" ]; } ]; diff --git a/nixos/modules/system/boot/systemd.nix b/nixos/modules/system/boot/systemd.nix index 9e3c6149f92..2167df60bc9 100644 --- a/nixos/modules/system/boot/systemd.nix +++ b/nixos/modules/system/boot/systemd.nix @@ -240,7 +240,7 @@ let serviceConfig = { name, config, ... }: { config = mkMerge [ { # Default path for systemd services. Should be quite minimal. - path = + path = mkAfter [ pkgs.coreutils pkgs.findutils pkgs.gnugrep @@ -408,7 +408,6 @@ let in { - ###### interface options = { @@ -594,17 +593,33 @@ in each other's limit. The value may be specified in the following units: s, min, h, ms, us. To turn off any kind of rate limiting, set either value to 0. + + See <option>services.journald.rateLimitBurst</option> for important + considerations when setting this value. ''; }; services.journald.rateLimitBurst = mkOption { - default = 1000; + default = 10000; type = types.int; description = '' Configures the rate limiting burst limit (number of messages per interval) that is applied to all messages generated on the system. This rate limiting is applied per-service, so that two services which log do not interfere with each other's limit. + + Note that the effective rate limit is multiplied by a factor derived + from the available free disk space for the journal as described on + <link xlink:href="https://www.freedesktop.org/software/systemd/man/journald.conf.html"> + journald.conf(5)</link>. + + Note that the total amount of logs stored is limited by journald settings + such as <literal>SystemMaxUse</literal>, which defaults to a 4 GB cap. + + It is thus recommended to compute what period of time that you will be + able to store logs for when an application logs at full burst rate. + With default settings for log lines that are 100 Bytes long, this can + amount to just a few hours. ''; }; @@ -698,6 +713,16 @@ in ''; }; + systemd.sleep.extraConfig = mkOption { + default = ""; + type = types.lines; + example = "HibernateDelaySec=1h"; + description = '' + Extra config options for systemd sleep state logic. + See sleep.conf.d(5) man page for available options. + ''; + }; + systemd.user.extraConfig = mkOption { default = ""; type = types.lines; @@ -777,6 +802,18 @@ in ''; }; + systemd.suppressedSystemUnits = mkOption { + default = [ ]; + type = types.listOf types.str; + example = [ "systemd-backlight@.service" ]; + description = '' + A list of units to suppress when generating system systemd configuration directory. This has + priority over upstream units, <option>systemd.units</option>, and + <option>systemd.additionalUpstreamSystemUnits</option>. The main purpose of this is to + suppress a upstream systemd unit with any modifications made to it by other NixOS modules. + ''; + }; + }; @@ -809,8 +846,11 @@ in done ${concatStrings (mapAttrsToList (exec: target: "ln -s ${target} $out/${exec};\n") links)} ''; + + enabledUpstreamSystemUnits = filter (n: ! elem n cfg.suppressedSystemUnits) upstreamSystemUnits; + enabledUnits = filterAttrs (n: v: ! elem n cfg.suppressedSystemUnits) cfg.units; in ({ - "systemd/system".source = generateUnits "system" cfg.units upstreamSystemUnits upstreamSystemWants; + "systemd/system".source = generateUnits "system" enabledUnits enabledUpstreamSystemUnits upstreamSystemWants; "systemd/user".source = generateUnits "user" cfg.user.units upstreamUserUnits []; @@ -818,7 +858,6 @@ in [Manager] ${optionalString config.systemd.enableCgroupAccounting '' DefaultCPUAccounting=yes - DefaultBlockIOAccounting=yes DefaultIOAccounting=yes DefaultBlockIOAccounting=yes DefaultIPAccounting=yes @@ -864,27 +903,32 @@ in "systemd/sleep.conf".text = '' [Sleep] + ${config.systemd.sleep.extraConfig} ''; # install provided sysctl snippets "sysctl.d/50-coredump.conf".source = "${systemd}/example/sysctl.d/50-coredump.conf"; "sysctl.d/50-default.conf".source = "${systemd}/example/sysctl.d/50-default.conf"; + "tmpfiles.d/00-nixos.conf".text = '' + # This file is created automatically and should not be modified. + # Please change the option ‘systemd.tmpfiles.rules’ instead. + + ${concatStringsSep "\n" cfg.tmpfiles.rules} + ''; + + "tmpfiles.d/home.conf".source = "${systemd}/example/tmpfiles.d/home.conf"; "tmpfiles.d/journal-nocow.conf".source = "${systemd}/example/tmpfiles.d/journal-nocow.conf"; + "tmpfiles.d/portables.conf".source = "${systemd}/example/tmpfiles.d/portables.conf"; "tmpfiles.d/static-nodes-permissions.conf".source = "${systemd}/example/tmpfiles.d/static-nodes-permissions.conf"; "tmpfiles.d/systemd.conf".source = "${systemd}/example/tmpfiles.d/systemd.conf"; + "tmpfiles.d/systemd-nologin.conf".source = "${systemd}/example/tmpfiles.d/systemd-nologin.conf"; "tmpfiles.d/systemd-nspawn.conf".source = "${systemd}/example/tmpfiles.d/systemd-nspawn.conf"; "tmpfiles.d/systemd-tmp.conf".source = "${systemd}/example/tmpfiles.d/systemd-tmp.conf"; + "tmpfiles.d/tmp.conf".source = "${systemd}/example/tmpfiles.d/tmp.conf"; "tmpfiles.d/var.conf".source = "${systemd}/example/tmpfiles.d/var.conf"; "tmpfiles.d/x11.conf".source = "${systemd}/example/tmpfiles.d/x11.conf"; - "tmpfiles.d/nixos.conf".text = '' - # This file is created automatically and should not be modified. - # Please change the option ‘systemd.tmpfiles.rules’ instead. - - ${concatStringsSep "\n" cfg.tmpfiles.rules} - ''; - "systemd/system-generators" = { source = hooks "generators" cfg.generators; }; "systemd/system-shutdown" = { source = hooks "shutdown" cfg.shutdown; }; }); @@ -1006,5 +1050,7 @@ in [ (mkRenamedOptionModule [ "boot" "systemd" "sockets" ] [ "systemd" "sockets" ]) (mkRenamedOptionModule [ "boot" "systemd" "targets" ] [ "systemd" "targets" ]) (mkRenamedOptionModule [ "boot" "systemd" "services" ] [ "systemd" "services" ]) + (mkRenamedOptionModule [ "jobs" ] [ "systemd" "services" ]) + (mkRemovedOptionModule [ "systemd" "generator-packages" ] "Use systemd.packages instead.") ]; } |