diff options
Diffstat (limited to 'nixpkgs/nixos/modules/system/boot')
69 files changed, 15489 insertions, 0 deletions
diff --git a/nixpkgs/nixos/modules/system/boot/binfmt.nix b/nixpkgs/nixos/modules/system/boot/binfmt.nix new file mode 100644 index 000000000000..08e3dce70844 --- /dev/null +++ b/nixpkgs/nixos/modules/system/boot/binfmt.nix @@ -0,0 +1,338 @@ +{ config, lib, pkgs, ... }: +let + inherit (lib) mkOption mkDefault types optionalString; + + cfg = config.boot.binfmt; + + makeBinfmtLine = name: { recognitionType, offset, magicOrExtension + , mask, preserveArgvZero, openBinary + , matchCredentials, fixBinary, ... + }: let + type = if recognitionType == "magic" then "M" else "E"; + offset' = toString offset; + mask' = toString mask; + interpreter = "/run/binfmt/${name}"; + flags = if !(matchCredentials -> openBinary) + then throw "boot.binfmt.registrations.${name}: you can't specify openBinary = false when matchCredentials = true." + else optionalString preserveArgvZero "P" + + optionalString (openBinary && !matchCredentials) "O" + + optionalString matchCredentials "C" + + optionalString fixBinary "F"; + in ":${name}:${type}:${offset'}:${magicOrExtension}:${mask'}:${interpreter}:${flags}"; + + mkInterpreter = name: { interpreter, wrapInterpreterInShell, ... }: + if wrapInterpreterInShell + then pkgs.writeShellScript "${name}-interpreter" '' + #!${pkgs.bash}/bin/sh + exec -- ${interpreter} "$@" + '' + else interpreter; + + getEmulator = system: (lib.systems.elaborate { inherit system; }).emulator pkgs; + getQemuArch = system: (lib.systems.elaborate { inherit system; }).qemuArch; + + # Mapping of systems to “magicOrExtension” and “mask”. Mostly taken from: + # - https://github.com/cleverca22/nixos-configs/blob/master/qemu.nix + # and + # - https://github.com/qemu/qemu/blob/master/scripts/qemu-binfmt-conf.sh + # TODO: maybe put these in a JSON file? + magics = { + armv6l-linux = { + magicOrExtension = ''\x7fELF\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x28\x00''; + mask = ''\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\x00\xff\xfe\xff\xff\xff''; + }; + armv7l-linux = { + magicOrExtension = ''\x7fELF\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x28\x00''; + mask = ''\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\x00\xff\xfe\xff\xff\xff''; + }; + aarch64-linux = { + magicOrExtension = ''\x7fELF\x02\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\xb7\x00''; + mask = ''\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\x00\xff\xfe\xff\xff\xff''; + }; + aarch64_be-linux = { + magicOrExtension = ''\x7fELF\x02\x02\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\xb7''; + mask = ''\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff''; + }; + i386-linux = { + magicOrExtension = ''\x7fELF\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x03\x00''; + mask = ''\xff\xff\xff\xff\xff\xfe\xfe\x00\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff''; + }; + i486-linux = { + magicOrExtension = ''\x7fELF\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x06\x00''; + mask = ''\xff\xff\xff\xff\xff\xfe\xfe\x00\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff''; + }; + i586-linux = { + magicOrExtension = ''\x7fELF\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x06\x00''; + mask = ''\xff\xff\xff\xff\xff\xfe\xfe\x00\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff''; + }; + i686-linux = { + magicOrExtension = ''\x7fELF\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x06\x00''; + mask = ''\xff\xff\xff\xff\xff\xfe\xfe\x00\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff''; + }; + x86_64-linux = { + magicOrExtension = ''\x7fELF\x02\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x3e\x00''; + mask = ''\xff\xff\xff\xff\xff\xfe\xfe\x00\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff''; + }; + alpha-linux = { + magicOrExtension = ''\x7fELF\x02\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x26\x90''; + mask = ''\xff\xff\xff\xff\xff\xfe\xfe\x00\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff''; + }; + sparc64-linux = { + magicOrExtension = ''\x7fELF\x01\x02\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x02''; + mask = ''\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff''; + }; + sparc-linux = { + magicOrExtension = ''\x7fELF\x01\x02\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x12''; + mask = ''\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff''; + }; + powerpc-linux = { + magicOrExtension = ''\x7fELF\x01\x02\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x14''; + mask = ''\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff''; + }; + powerpc64-linux = { + magicOrExtension = ''\x7fELF\x02\x02\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x15''; + mask = ''\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff''; + }; + powerpc64le-linux = { + magicOrExtension = ''\x7fELF\x02\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x15\x00''; + mask = ''\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\x00''; + }; + mips-linux = { + magicOrExtension = ''\x7fELF\x01\x02\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x08\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00''; + mask = ''\xff\xff\xff\xff\xff\xff\xff\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff\xff\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x20''; + }; + mipsel-linux = { + magicOrExtension = ''\x7fELF\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x08\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00''; + mask = ''\xff\xff\xff\xff\xff\xff\xff\x00\x00\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff\xff\xff\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x20\x00\x00\x00''; + }; + mips64-linux = { + magicOrExtension = ''\x7fELF\x02\x02\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x08''; + mask = ''\xff\xff\xff\xff\xff\xff\xff\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff''; + }; + mips64el-linux = { + magicOrExtension = ''\x7fELF\x02\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x08\x00''; + mask = ''\xff\xff\xff\xff\xff\xff\xff\x00\x00\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff''; + }; + mips64-linuxabin32 = { + magicOrExtension = ''\x7fELF\x01\x02\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x08\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x20''; + mask = ''\xff\xff\xff\xff\xff\xff\xff\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff\xff\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x20''; + }; + mips64el-linuxabin32 = { + magicOrExtension = ''\x7fELF\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x08\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x20\x00\x00\x00''; + mask = ''\xff\xff\xff\xff\xff\xff\xff\x00\x00\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff\xff\xff\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x20\x00\x00\x00''; + }; + riscv32-linux = { + magicOrExtension = ''\x7fELF\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\xf3\x00''; + mask = ''\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff''; + }; + riscv64-linux = { + magicOrExtension = ''\x7fELF\x02\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\xf3\x00''; + mask = ''\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff''; + }; + loongarch64-linux = { + magicOrExtension = ''\x7fELF\x02\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x02\x01''; + mask = ''\xff\xff\xff\xff\xff\xff\xff\xfc\x00\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff''; + }; + wasm32-wasi = { + magicOrExtension = ''\x00asm''; + mask = ''\xff\xff\xff\xff''; + }; + wasm64-wasi = { + magicOrExtension = ''\x00asm''; + mask = ''\xff\xff\xff\xff''; + }; + x86_64-windows.magicOrExtension = "MZ"; + i686-windows.magicOrExtension = "MZ"; + }; + +in { + imports = [ + (lib.mkRenamedOptionModule [ "boot" "binfmtMiscRegistrations" ] [ "boot" "binfmt" "registrations" ]) + ]; + + options = { + boot.binfmt = { + registrations = mkOption { + default = {}; + + description = lib.mdDoc '' + Extra binary formats to register with the kernel. + See https://www.kernel.org/doc/html/latest/admin-guide/binfmt-misc.html for more details. + ''; + + type = types.attrsOf (types.submodule ({ config, ... }: { + options = { + recognitionType = mkOption { + default = "magic"; + description = lib.mdDoc "Whether to recognize executables by magic number or extension."; + type = types.enum [ "magic" "extension" ]; + }; + + offset = mkOption { + default = null; + description = lib.mdDoc "The byte offset of the magic number used for recognition."; + type = types.nullOr types.int; + }; + + magicOrExtension = mkOption { + description = lib.mdDoc "The magic number or extension to match on."; + type = types.str; + }; + + mask = mkOption { + default = null; + description = + lib.mdDoc "A mask to be ANDed with the byte sequence of the file before matching"; + type = types.nullOr types.str; + }; + + interpreter = mkOption { + description = lib.mdDoc '' + The interpreter to invoke to run the program. + + Note that the actual registration will point to + /run/binfmt/''${name}, so the kernel interpreter length + limit doesn't apply. + ''; + type = types.path; + }; + + preserveArgvZero = mkOption { + default = false; + description = lib.mdDoc '' + Whether to pass the original argv[0] to the interpreter. + + See the description of the 'P' flag in the kernel docs + for more details; + ''; + type = types.bool; + }; + + openBinary = mkOption { + default = config.matchCredentials; + description = lib.mdDoc '' + Whether to pass the binary to the interpreter as an open + file descriptor, instead of a path. + ''; + type = types.bool; + }; + + matchCredentials = mkOption { + default = false; + description = lib.mdDoc '' + Whether to launch with the credentials and security + token of the binary, not the interpreter (e.g. setuid + bit). + + See the description of the 'C' flag in the kernel docs + for more details. + + Implies/requires openBinary = true. + ''; + type = types.bool; + }; + + fixBinary = mkOption { + default = false; + description = lib.mdDoc '' + Whether to open the interpreter file as soon as the + registration is loaded, rather than waiting for a + relevant file to be invoked. + + See the description of the 'F' flag in the kernel docs + for more details. + ''; + type = types.bool; + }; + + wrapInterpreterInShell = mkOption { + default = true; + description = lib.mdDoc '' + Whether to wrap the interpreter in a shell script. + + This allows a shell command to be set as the interpreter. + ''; + type = types.bool; + }; + + interpreterSandboxPath = mkOption { + internal = true; + default = null; + description = lib.mdDoc '' + Path of the interpreter to expose in the build sandbox. + ''; + type = types.nullOr types.path; + }; + }; + })); + }; + + emulatedSystems = mkOption { + default = []; + example = [ "wasm32-wasi" "x86_64-windows" "aarch64-linux" ]; + description = lib.mdDoc '' + List of systems to emulate. Will also configure Nix to + support your new systems. + Warning: the builder can execute all emulated systems within the same build, which introduces impurities in the case of cross compilation. + ''; + type = types.listOf (types.enum (builtins.attrNames magics)); + }; + }; + }; + + config = { + boot.binfmt.registrations = builtins.listToAttrs (map (system: { + name = system; + value = { config, ... }: let + interpreter = getEmulator system; + qemuArch = getQemuArch system; + + preserveArgvZero = "qemu-${qemuArch}" == baseNameOf interpreter; + interpreterReg = let + wrapperName = "qemu-${qemuArch}-binfmt-P"; + wrapper = pkgs.wrapQemuBinfmtP wrapperName interpreter; + in + if preserveArgvZero then "${wrapper}/bin/${wrapperName}" + else interpreter; + in ({ + preserveArgvZero = mkDefault preserveArgvZero; + + interpreter = mkDefault interpreterReg; + wrapInterpreterInShell = mkDefault (!config.preserveArgvZero); + interpreterSandboxPath = mkDefault (dirOf (dirOf config.interpreter)); + } // (magics.${system} or (throw "Cannot create binfmt registration for system ${system}"))); + }) cfg.emulatedSystems); + nix.settings = lib.mkIf (cfg.emulatedSystems != []) { + extra-platforms = cfg.emulatedSystems ++ lib.optional pkgs.stdenv.hostPlatform.isx86_64 "i686-linux"; + extra-sandbox-paths = let + ruleFor = system: cfg.registrations.${system}; + hasWrappedRule = lib.any (system: (ruleFor system).wrapInterpreterInShell) cfg.emulatedSystems; + in [ "/run/binfmt" ] + ++ lib.optional hasWrappedRule "${pkgs.bash}" + ++ (map (system: (ruleFor system).interpreterSandboxPath) cfg.emulatedSystems); + }; + + environment.etc."binfmt.d/nixos.conf".source = builtins.toFile "binfmt_nixos.conf" + (lib.concatStringsSep "\n" (lib.mapAttrsToList makeBinfmtLine config.boot.binfmt.registrations)); + + systemd = lib.mkMerge [ + ({ tmpfiles.rules = [ + "d /run/binfmt 0755 -" + ] ++ lib.mapAttrsToList + (name: interpreter: + "L+ /run/binfmt/${name} - - - - ${interpreter}" + ) + (lib.mapAttrs mkInterpreter config.boot.binfmt.registrations); + }) + + (lib.mkIf (config.boot.binfmt.registrations != {}) { + additionalUpstreamSystemUnits = [ + "proc-sys-fs-binfmt_misc.automount" + "proc-sys-fs-binfmt_misc.mount" + "systemd-binfmt.service" + ]; + services.systemd-binfmt.restartTriggers = [ (builtins.toJSON config.boot.binfmt.registrations) ]; + }) + ]; + }; +} diff --git a/nixpkgs/nixos/modules/system/boot/clevis.md b/nixpkgs/nixos/modules/system/boot/clevis.md new file mode 100644 index 000000000000..dcbf55de60a8 --- /dev/null +++ b/nixpkgs/nixos/modules/system/boot/clevis.md @@ -0,0 +1,51 @@ +# Clevis {#module-boot-clevis} + +[Clevis](https://github.com/latchset/clevis) +is a framework for automated decryption of resources. +Clevis allows for secure unattended disk decryption during boot, using decryption policies that must be satisfied for the data to decrypt. + + +## Create a JWE file containing your secret {#module-boot-clevis-create-secret} + +The first step is to embed your secret in a [JWE](https://en.wikipedia.org/wiki/JSON_Web_Encryption) file. +JWE files have to be created through the clevis command line. 3 types of policies are supported: + +1) TPM policies + +Secrets are pinned against the presence of a TPM2 device, for example: +``` +echo -n hi | clevis encrypt tpm2 '{}' > hi.jwe +``` +2) Tang policies + +Secrets are pinned against the presence of a Tang server, for example: +``` +echo -n hi | clevis encrypt tang '{"url": "http://tang.local"}' > hi.jwe +``` + +3) Shamir Secret Sharing + +Using Shamir's Secret Sharing ([sss](https://en.wikipedia.org/wiki/Shamir%27s_secret_sharing)), secrets are pinned using a combination of the two preceding policies. For example: +``` +echo -n hi | clevis encrypt sss \ +'{"t": 2, "pins": {"tpm2": {"pcr_ids": "0"}, "tang": {"url": "http://tang.local"}}}' \ +> hi.jwe +``` + +For more complete documentation on how to generate a secret with clevis, see the [clevis documentation](https://github.com/latchset/clevis). + + +## Activate unattended decryption of a resource at boot {#module-boot-clevis-activate} + +In order to activate unattended decryption of a resource at boot, enable the `clevis` module: + +``` +boot.initrd.clevis.enable = true; +``` + +Then, specify the device you want to decrypt using a given clevis secret. Clevis will automatically try to decrypt the device at boot and will fallback to interactive unlocking if the decryption policy is not fulfilled. +``` +boot.initrd.clevis.devices."/dev/nvme0n1p1".secretFile = ./nvme0n1p1.jwe; +``` + +Only `bcachefs`, `zfs` and `luks` encrypted devices are supported at this time. diff --git a/nixpkgs/nixos/modules/system/boot/clevis.nix b/nixpkgs/nixos/modules/system/boot/clevis.nix new file mode 100644 index 000000000000..0c72590f9385 --- /dev/null +++ b/nixpkgs/nixos/modules/system/boot/clevis.nix @@ -0,0 +1,107 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + cfg = config.boot.initrd.clevis; + systemd = config.boot.initrd.systemd; + supportedFs = [ "zfs" "bcachefs" ]; +in +{ + meta.maintainers = with maintainers; [ julienmalka camillemndn ]; + meta.doc = ./clevis.md; + + options = { + boot.initrd.clevis.enable = mkEnableOption (lib.mdDoc "Clevis in initrd"); + + + boot.initrd.clevis.package = mkOption { + type = types.package; + default = pkgs.clevis; + defaultText = "pkgs.clevis"; + description = lib.mdDoc "Clevis package"; + }; + + boot.initrd.clevis.devices = mkOption { + description = "Encrypted devices that need to be unlocked at boot using Clevis"; + default = { }; + type = types.attrsOf (types.submodule ({ + options.secretFile = mkOption { + description = lib.mdDoc "Clevis JWE file used to decrypt the device at boot, in concert with the chosen pin (one of TPM2, Tang server, or SSS)."; + type = types.path; + }; + })); + }; + + boot.initrd.clevis.useTang = mkOption { + description = "Whether the Clevis JWE file used to decrypt the devices uses a Tang server as a pin."; + default = false; + type = types.bool; + }; + + }; + + config = mkIf cfg.enable { + + # Implementation of clevis unlocking for the supported filesystems are located directly in the respective modules. + + + assertions = (attrValues (mapAttrs + (device: _: { + assertion = (any (fs: fs.device == device && (elem fs.fsType supportedFs)) config.system.build.fileSystems) || (hasAttr device config.boot.initrd.luks.devices); + message = '' + No filesystem or LUKS device with the name ${device} is declared in your configuration.''; + }) + cfg.devices)); + + + warnings = + if cfg.useTang && !config.boot.initrd.network.enable && !config.boot.initrd.systemd.network.enable + then [ "In order to use a Tang pinned secret you must configure networking in initrd" ] + else [ ]; + + boot.initrd = { + extraUtilsCommands = mkIf (!systemd.enable) '' + copy_bin_and_libs ${pkgs.jose}/bin/jose + copy_bin_and_libs ${pkgs.curl}/bin/curl + copy_bin_and_libs ${pkgs.bash}/bin/bash + + copy_bin_and_libs ${pkgs.tpm2-tools}/bin/.tpm2-wrapped + mv $out/bin/{.tpm2-wrapped,tpm2} + cp {${pkgs.tpm2-tss},$out}/lib/libtss2-tcti-device.so.0 + + copy_bin_and_libs ${cfg.package}/bin/.clevis-wrapped + mv $out/bin/{.clevis-wrapped,clevis} + + for BIN in ${cfg.package}/bin/clevis-decrypt*; do + copy_bin_and_libs $BIN + done + + for BIN in $out/bin/clevis{,-decrypt{,-null,-tang,-tpm2}}; do + sed -i $BIN -e 's,${pkgs.bash},,' -e 's,${pkgs.coreutils},,' + done + + sed -i $out/bin/clevis-decrypt-tpm2 -e 's,tpm2_,tpm2 ,' + ''; + + secrets = lib.mapAttrs' (name: value: nameValuePair "/etc/clevis/${name}.jwe" value.secretFile) cfg.devices; + + systemd = { + extraBin = mkIf systemd.enable { + clevis = "${cfg.package}/bin/clevis"; + curl = "${pkgs.curl}/bin/curl"; + }; + + storePaths = mkIf systemd.enable [ + cfg.package + "${pkgs.jose}/bin/jose" + "${pkgs.curl}/bin/curl" + "${pkgs.tpm2-tools}/bin/tpm2_createprimary" + "${pkgs.tpm2-tools}/bin/tpm2_flushcontext" + "${pkgs.tpm2-tools}/bin/tpm2_load" + "${pkgs.tpm2-tools}/bin/tpm2_unseal" + ]; + }; + }; + }; +} diff --git a/nixpkgs/nixos/modules/system/boot/emergency-mode.nix b/nixpkgs/nixos/modules/system/boot/emergency-mode.nix new file mode 100644 index 000000000000..a2163aa5ffb3 --- /dev/null +++ b/nixpkgs/nixos/modules/system/boot/emergency-mode.nix @@ -0,0 +1,37 @@ +{ config, lib, ... }: + +with lib; + +{ + + ###### interface + + options = { + + systemd.enableEmergencyMode = mkOption { + default = true; + type = types.bool; + description = lib.mdDoc '' + Whether to enable emergency mode, which is an + {command}`sulogin` shell started on the console if + mounting a filesystem fails. Since some machines (like EC2 + instances) have no console of any kind, emergency mode doesn't + make sense, and it's better to continue with the boot insofar + as possible. + ''; + }; + + }; + + ###### implementation + + config = { + + systemd.additionalUpstreamSystemUnits = optionals + config.systemd.enableEmergencyMode [ + "emergency.target" "emergency.service" + ]; + + }; + +} diff --git a/nixpkgs/nixos/modules/system/boot/grow-partition.nix b/nixpkgs/nixos/modules/system/boot/grow-partition.nix new file mode 100644 index 000000000000..8a0fc3a03dac --- /dev/null +++ b/nixpkgs/nixos/modules/system/boot/grow-partition.nix @@ -0,0 +1,54 @@ +# This module automatically grows the root partition. +# This allows an instance to be created with a bigger root filesystem +# than provided by the machine image. + +{ config, lib, pkgs, ... }: + +with lib; + +{ + imports = [ + (mkRenamedOptionModule [ "virtualisation" "growPartition" ] [ "boot" "growPartition" ]) + ]; + + options = { + boot.growPartition = mkEnableOption (lib.mdDoc "growing the root partition on boot"); + }; + + config = mkIf config.boot.growPartition { + assertions = [ + { + assertion = !config.boot.initrd.systemd.repart.enable && !config.systemd.repart.enable; + message = "systemd-repart already grows the root partition and thus you should not use boot.growPartition"; + } + ]; + systemd.services.growpart = { + wantedBy = [ "-.mount" ]; + after = [ "-.mount" ]; + before = [ "systemd-growfs-root.service" "shutdown.target" ]; + conflicts = [ "shutdown.target" ]; + unitConfig.DefaultDependencies = false; + serviceConfig = { + Type = "oneshot"; + RemainAfterExit = true; + TimeoutSec = "infinity"; + # growpart returns 1 if the partition is already grown + SuccessExitStatus = "0 1"; + }; + + script = '' + rootDevice="${config.fileSystems."/".device}" + rootDevice="$(readlink -f "$rootDevice")" + parentDevice="$rootDevice" + while [ "''${parentDevice%[0-9]}" != "''${parentDevice}" ]; do + parentDevice="''${parentDevice%[0-9]}"; + done + partNum="''${rootDevice#''${parentDevice}}" + if [ "''${parentDevice%[0-9]p}" != "''${parentDevice}" ] && [ -b "''${parentDevice%p}" ]; then + parentDevice="''${parentDevice%p}" + fi + "${pkgs.cloud-utils.guest}/bin/growpart" "$parentDevice" "$partNum" + ''; + }; + }; +} diff --git a/nixpkgs/nixos/modules/system/boot/initrd-network.nix b/nixpkgs/nixos/modules/system/boot/initrd-network.nix new file mode 100644 index 000000000000..88ba43caf003 --- /dev/null +++ b/nixpkgs/nixos/modules/system/boot/initrd-network.nix @@ -0,0 +1,162 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + + cfg = config.boot.initrd.network; + + dhcpInterfaces = lib.attrNames (lib.filterAttrs (iface: v: v.useDHCP == true) (config.networking.interfaces or {})); + doDhcp = cfg.udhcpc.enable || dhcpInterfaces != []; + dhcpIfShellExpr = if config.networking.useDHCP || cfg.udhcpc.enable + then "$(ls /sys/class/net/ | grep -v ^lo$)" + else lib.concatMapStringsSep " " lib.escapeShellArg dhcpInterfaces; + + udhcpcScript = pkgs.writeScript "udhcp-script" + '' + #! /bin/sh + if [ "$1" = bound ]; then + ip address add "$ip/$mask" dev "$interface" + if [ -n "$mtu" ]; then + ip link set mtu "$mtu" dev "$interface" + fi + if [ -n "$staticroutes" ]; then + echo "$staticroutes" \ + | sed -r "s@(\S+) (\S+)@ ip route add \"\1\" via \"\2\" dev \"$interface\" ; @g" \ + | sed -r "s@ via \"0\.0\.0\.0\"@@g" \ + | /bin/sh + fi + if [ -n "$router" ]; then + ip route add "$router" dev "$interface" # just in case if "$router" is not within "$ip/$mask" (e.g. Hetzner Cloud) + ip route add default via "$router" dev "$interface" + fi + if [ -n "$dns" ]; then + rm -f /etc/resolv.conf + for server in $dns; do + echo "nameserver $server" >> /etc/resolv.conf + done + fi + fi + ''; + + udhcpcArgs = toString cfg.udhcpc.extraArgs; + +in + +{ + + options = { + + boot.initrd.network.enable = mkOption { + type = types.bool; + default = false; + description = lib.mdDoc '' + Add network connectivity support to initrd. The network may be + configured using the `ip` kernel parameter, + as described in [the kernel documentation](https://www.kernel.org/doc/Documentation/filesystems/nfs/nfsroot.txt). + Otherwise, if + {option}`networking.useDHCP` is enabled, an IP address + is acquired using DHCP. + + You should add the module(s) required for your network card to + boot.initrd.availableKernelModules. + `lspci -v | grep -iA8 'network\|ethernet'` + will tell you which. + ''; + }; + + boot.initrd.network.flushBeforeStage2 = mkOption { + type = types.bool; + default = !config.boot.initrd.systemd.enable; + defaultText = "!config.boot.initrd.systemd.enable"; + description = lib.mdDoc '' + 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. + + The default is false when systemd is enabled in initrd, + because the systemd-networkd documentation suggests it. + ''; + }; + + boot.initrd.network.udhcpc.enable = mkOption { + default = config.networking.useDHCP && !config.boot.initrd.systemd.enable; + defaultText = "networking.useDHCP"; + type = types.bool; + description = lib.mdDoc '' + Enables the udhcpc service during stage 1 of the boot process. This + defaults to {option}`networking.useDHCP`. Therefore, this useful if + useDHCP is off but the initramfs should do dhcp. + ''; + }; + + boot.initrd.network.udhcpc.extraArgs = mkOption { + default = []; + type = types.listOf types.str; + description = lib.mdDoc '' + Additional command-line arguments passed verbatim to + udhcpc if {option}`boot.initrd.network.enable` and + {option}`boot.initrd.network.udhcpc.enable` are enabled. + ''; + }; + + boot.initrd.network.postCommands = mkOption { + default = ""; + type = types.lines; + description = lib.mdDoc '' + Shell commands to be executed after stage 1 of the + boot has initialised the network. + ''; + }; + + + }; + + config = mkIf cfg.enable { + + boot.initrd.kernelModules = [ "af_packet" ]; + + boot.initrd.extraUtilsCommands = mkIf (!config.boot.initrd.systemd.enable) '' + copy_bin_and_libs ${pkgs.klibc}/lib/klibc/bin.static/ipconfig + ''; + + boot.initrd.preLVMCommands = mkIf (!config.boot.initrd.systemd.enable) (mkBefore ( + # Search for interface definitions in command line. + '' + ifaces="" + for o in $(cat /proc/cmdline); do + case $o in + ip=*) + ipconfig $o && ifaces="$ifaces $(echo $o | cut -d: -f6)" + ;; + esac + done + '' + + # Otherwise, use DHCP. + + optionalString doDhcp '' + # Bring up all interfaces. + for iface in ${dhcpIfShellExpr}; do + echo "bringing up network interface $iface..." + ip link set dev "$iface" up && ifaces="$ifaces $iface" + done + + # 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 + '' + + + cfg.postCommands)); + + boot.initrd.postMountCommands = mkIf (cfg.flushBeforeStage2 && !config.boot.initrd.systemd.enable) '' + for iface in $ifaces; do + ip address flush dev "$iface" + ip link set dev "$iface" down + done + ''; + + }; + +} diff --git a/nixpkgs/nixos/modules/system/boot/initrd-openvpn.nix b/nixpkgs/nixos/modules/system/boot/initrd-openvpn.nix new file mode 100644 index 000000000000..2530240628e4 --- /dev/null +++ b/nixpkgs/nixos/modules/system/boot/initrd-openvpn.nix @@ -0,0 +1,91 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + + cfg = config.boot.initrd.network.openvpn; + +in + +{ + + options = { + + boot.initrd.network.openvpn.enable = mkOption { + type = types.bool; + default = false; + description = lib.mdDoc '' + Starts an OpenVPN client during initrd boot. It can be used to e.g. + remotely accessing the SSH service controlled by + {option}`boot.initrd.network.ssh` or other network services + included. Service is killed when stage-1 boot is finished. + ''; + }; + + boot.initrd.network.openvpn.configuration = mkOption { + type = types.path; # Same type as boot.initrd.secrets + description = lib.mdDoc '' + The configuration file for OpenVPN. + + ::: {.warning} + Unless your bootloader supports initrd secrets, this configuration + is stored insecurely in the global Nix store. + ::: + ''; + example = literalExpression "./configuration.ovpn"; + }; + + }; + + config = mkIf (config.boot.initrd.network.enable && cfg.enable) { + assertions = [ + { + assertion = cfg.configuration != null; + message = "You should specify a configuration for initrd OpenVPN"; + } + ]; + + # Add kernel modules needed for OpenVPN + boot.initrd.kernelModules = [ "tun" "tap" ]; + + # Add openvpn and ip binaries to the initrd + # The shared libraries are required for DNS resolution + boot.initrd.extraUtilsCommands = mkIf (!config.boot.initrd.systemd.enable) '' + copy_bin_and_libs ${pkgs.openvpn}/bin/openvpn + copy_bin_and_libs ${pkgs.iproute2}/bin/ip + + cp -pv ${pkgs.glibc}/lib/libresolv.so.2 $out/lib + cp -pv ${pkgs.glibc}/lib/libnss_dns.so.2 $out/lib + ''; + + boot.initrd.systemd.storePaths = [ + "${pkgs.openvpn}/bin/openvpn" + "${pkgs.iproute2}/bin/ip" + "${pkgs.glibc}/lib/libresolv.so.2" + "${pkgs.glibc}/lib/libnss_dns.so.2" + ]; + + boot.initrd.secrets = { + "/etc/initrd.ovpn" = cfg.configuration; + }; + + # openvpn --version would exit with 1 instead of 0 + boot.initrd.extraUtilsCommandsTest = mkIf (!config.boot.initrd.systemd.enable) '' + $out/bin/openvpn --show-gateway + ''; + + boot.initrd.network.postCommands = mkIf (!config.boot.initrd.systemd.enable) '' + openvpn /etc/initrd.ovpn & + ''; + + boot.initrd.systemd.services.openvpn = { + wantedBy = [ "initrd.target" ]; + path = [ pkgs.iproute2 ]; + after = [ "network.target" "initrd-nixos-copy-secrets.service" ]; + serviceConfig.ExecStart = "${pkgs.openvpn}/bin/openvpn /etc/initrd.ovpn"; + serviceConfig.Type = "notify"; + }; + }; + +} diff --git a/nixpkgs/nixos/modules/system/boot/initrd-ssh.nix b/nixpkgs/nixos/modules/system/boot/initrd-ssh.nix new file mode 100644 index 000000000000..61e61f32bc5e --- /dev/null +++ b/nixpkgs/nixos/modules/system/boot/initrd-ssh.nix @@ -0,0 +1,269 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + + cfg = config.boot.initrd.network.ssh; + shell = if cfg.shell == null then "/bin/ash" else cfg.shell; + inherit (config.programs.ssh) package; + + enabled = let initrd = config.boot.initrd; in (initrd.network.enable || initrd.systemd.network.enable) && cfg.enable; + +in + +{ + + options.boot.initrd.network.ssh = { + enable = mkOption { + type = types.bool; + default = false; + description = lib.mdDoc '' + 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`. + ''; + }; + + port = mkOption { + type = types.port; + default = 22; + description = lib.mdDoc '' + Port on which SSH initrd service should listen. + ''; + }; + + shell = mkOption { + type = types.nullOr types.str; + default = null; + defaultText = ''"/bin/ash"''; + description = lib.mdDoc '' + Login shell of the remote user. Can be used to limit actions user can do. + ''; + }; + + 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 = lib.mdDoc '' + Specify SSH host keys to import into the initrd. + + To generate keys, use + {manpage}`ssh-keygen(1)` + as root: + + ``` + ssh-keygen -t rsa -N "" -f /etc/secrets/initrd/ssh_host_rsa_key + ssh-keygen -t ed25519 -N "" -f /etc/secrets/initrd/ssh_host_ed25519_key + ``` + + ::: {.warning} + 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! + + 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. + ::: + ''; + }; + + ignoreEmptyHostKeys = mkOption { + type = types.bool; + default = false; + description = lib.mdDoc '' + Allow leaving {option}`config.boot.initrd.network.ssh` empty, + to deploy ssh host keys out of band. + ''; + }; + + authorizedKeys = mkOption { + type = types.listOf types.str; + default = config.users.users.root.openssh.authorizedKeys.keys; + defaultText = literalExpression "config.users.users.root.openssh.authorizedKeys.keys"; + description = lib.mdDoc '' + Authorized keys for the root user on initrd. + ''; + }; + + extraConfig = mkOption { + type = types.lines; + default = ""; + description = lib.mdDoc "Verbatim contents of {file}`sshd_config`."; + }; + }; + + 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 = '' + UsePAM no + Port ${toString cfg.port} + + PasswordAuthentication no + AuthorizedKeysFile %h/.ssh/authorized_keys %h/.ssh/authorized_keys2 /etc/ssh/authorized_keys.d/%u + ChallengeResponseAuthentication no + + ${flip concatMapStrings cfg.hostKeys (path: '' + HostKey ${initrdKeyPath path} + '')} + + KexAlgorithms ${concatStringsSep "," sshdCfg.settings.KexAlgorithms} + Ciphers ${concatStringsSep "," sshdCfg.settings.Ciphers} + MACs ${concatStringsSep "," sshdCfg.settings.Macs} + + LogLevel ${sshdCfg.settings.LogLevel} + + ${if sshdCfg.settings.UseDns then '' + UseDNS yes + '' else '' + UseDNS no + ''} + + ${cfg.extraConfig} + ''; + in mkIf enabled { + assertions = [ + { + assertion = cfg.authorizedKeys != []; + message = "You should specify at least one authorized key for initrd SSH"; + } + + { + assertion = (cfg.hostKeys != []) || cfg.ignoreEmptyHostKeys; + message = '' + You must now pre-generate the host keys for initrd SSH. + See the boot.initrd.network.ssh.hostKeys documentation + for instructions. + ''; + } + ]; + + warnings = lib.optional (config.boot.initrd.systemd.enable && cfg.shell != null) '' + Please set 'boot.initrd.systemd.users.root.shell' instead of 'boot.initrd.network.ssh.shell' + ''; + + boot.initrd.extraUtilsCommands = mkIf (!config.boot.initrd.systemd.enable) '' + copy_bin_and_libs ${package}/bin/sshd + cp -pv ${pkgs.glibc.out}/lib/libnss_files.so.* $out/lib + ''; + + boot.initrd.extraUtilsCommandsTest = mkIf (!config.boot.initrd.systemd.enable) '' + # sshd requires a host key to check config, so we pass in the test's + tmpkey="$(mktemp initrd-ssh-testkey.XXXXXXXXXX)" + cp "${../../../tests/initrd-network-ssh/ssh_host_ed25519_key}" "$tmpkey" + # keys from Nix store are world-readable, which sshd doesn't like + chmod 600 "$tmpkey" + echo -n ${escapeShellArg sshdConfig} | + $out/bin/sshd -t -f /dev/stdin \ + -h "$tmpkey" + rm "$tmpkey" + ''; + + boot.initrd.network.postCommands = mkIf (!config.boot.initrd.systemd.enable) '' + echo '${shell}' > /etc/shells + echo 'root:x:0:0:root:/root:${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 /var/empty + touch /var/log/lastlog + + 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)} + + ${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.postMountCommands = mkIf (!config.boot.initrd.systemd.enable) '' + # 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); + + # Systemd initrd stuff + boot.initrd.systemd = mkIf config.boot.initrd.systemd.enable { + users.sshd = { uid = 1; group = "sshd"; }; + groups.sshd = { gid = 1; }; + + users.root.shell = mkIf (config.boot.initrd.network.ssh.shell != null) config.boot.initrd.network.ssh.shell; + + contents."/etc/ssh/authorized_keys.d/root".text = + concatStringsSep "\n" config.boot.initrd.network.ssh.authorizedKeys; + contents."/etc/ssh/sshd_config".text = sshdConfig; + storePaths = ["${package}/bin/sshd"]; + + services.sshd = { + description = "SSH Daemon"; + wantedBy = [ "initrd.target" ]; + after = [ "network.target" "initrd-nixos-copy-secrets.service" ]; + before = [ "shutdown.target" ]; + conflicts = [ "shutdown.target" ]; + + # Keys from Nix store are world-readable, which sshd doesn't + # like. If this were a real nix store and not the initrd, we + # neither would nor could do this + preStart = flip concatMapStrings cfg.hostKeys (path: '' + /bin/chmod 0600 "${initrdKeyPath path}" + ''); + unitConfig.DefaultDependencies = false; + serviceConfig = { + ExecStart = "${package}/bin/sshd -D -f /etc/ssh/sshd_config"; + Type = "simple"; + KillMode = "process"; + Restart = "on-failure"; + }; + }; + }; + + }; + +} diff --git a/nixpkgs/nixos/modules/system/boot/kernel.nix b/nixpkgs/nixos/modules/system/boot/kernel.nix new file mode 100644 index 000000000000..a46331ccd431 --- /dev/null +++ b/nixpkgs/nixos/modules/system/boot/kernel.nix @@ -0,0 +1,429 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + + inherit (config.boot) kernelPatches; + inherit (config.boot.kernel) features randstructSeed; + inherit (config.boot.kernelPackages) kernel; + + kernelModulesConf = pkgs.writeText "nixos.conf" + '' + ${concatStringsSep "\n" config.boot.kernelModules} + ''; + +in + +{ + + ###### interface + + options = { + boot.kernel.enable = mkEnableOption (lib.mdDoc "the Linux kernel. This is useful for systemd-like containers which do not require a kernel") // { + default = true; + }; + + boot.kernel.features = mkOption { + default = {}; + example = literalExpression "{ debug = true; }"; + internal = true; + description = lib.mdDoc '' + This option allows to enable or disable certain kernel features. + It's not API, because it's about kernel feature sets, that + make sense for specific use cases. Mostly along with programs, + which would have separate nixos options. + `grep features pkgs/os-specific/linux/kernel/common-config.nix` + ''; + }; + + boot.kernelPackages = mkOption { + default = pkgs.linuxPackages; + type = types.raw; + apply = kernelPackages: kernelPackages.extend (self: super: { + kernel = super.kernel.override (originalArgs: { + inherit randstructSeed; + kernelPatches = (originalArgs.kernelPatches or []) ++ kernelPatches; + features = lib.recursiveUpdate super.kernel.features features; + }); + }); + # We don't want to evaluate all of linuxPackages for the manual + # - some of it might not even evaluate correctly. + defaultText = literalExpression "pkgs.linuxPackages"; + example = literalExpression "pkgs.linuxKernel.packages.linux_5_10"; + description = lib.mdDoc '' + This option allows you to override the Linux kernel used by + NixOS. Since things like external kernel module packages are + tied to the kernel you're using, it also overrides those. + This option is a function that takes Nixpkgs as an argument + (as a convenience), and returns an attribute set containing at + the very least an attribute {var}`kernel`. + Additional attributes may be needed depending on your + configuration. For instance, if you use the NVIDIA X driver, + then it also needs to contain an attribute + {var}`nvidia_x11`. + + Please note that we strictly support kernel versions that are + maintained by the Linux developers only. More information on the + availability of kernel versions is documented + [in the Linux section of the manual](https://nixos.org/manual/nixos/unstable/index.html#sec-kernel-config). + ''; + }; + + boot.kernelPatches = mkOption { + type = types.listOf types.attrs; + default = []; + example = literalExpression '' + [ + { + name = "foo"; + patch = ./foo.patch; + extraStructuredConfig.FOO = lib.kernel.yes; + features.foo = true; + } + ] + ''; + description = lib.mdDoc '' + A list of additional patches to apply to the kernel. + + Every item should be an attribute set with the following attributes: + + ```nix + { + name = "foo"; # descriptive name, required + + patch = ./foo.patch; # path or derivation that contains the patch source + # (required, but can be null if only config changes + # are needed) + + extraStructuredConfig = { # attrset of extra configuration parameters without the CONFIG_ prefix + FOO = lib.kernel.yes; # (optional) + }; # values should generally be lib.kernel.yes, + # lib.kernel.no or lib.kernel.module + + features = { # attrset of extra "features" the kernel is considered to have + foo = true; # (may be checked by other NixOS modules, optional) + }; + + extraConfig = "FOO y"; # extra configuration options in string form without the CONFIG_ prefix + # (optional, multiple lines allowed to specify multiple options) + # (deprecated, use extraStructuredConfig instead) + } + ``` + + There's a small set of existing kernel patches in Nixpkgs, available as `pkgs.kernelPatches`, + that follow this format and can be used directly. + ''; + }; + + boot.kernel.randstructSeed = mkOption { + type = types.str; + default = ""; + example = "my secret seed"; + description = lib.mdDoc '' + Provides a custom seed for the {var}`RANDSTRUCT` security + option of the Linux kernel. Note that {var}`RANDSTRUCT` is + only enabled in NixOS hardened kernels. Using a custom seed requires + building the kernel and dependent packages locally, since this + customization happens at build time. + ''; + }; + + boot.kernelParams = mkOption { + type = types.listOf (types.strMatching ''([^"[:space:]]|"[^"]*")+'' // { + name = "kernelParam"; + description = "string, with spaces inside double quotes"; + }); + default = [ ]; + description = lib.mdDoc "Parameters added to the kernel command line."; + }; + + boot.consoleLogLevel = mkOption { + type = types.int; + default = 4; + description = lib.mdDoc '' + The kernel console `loglevel`. All Kernel Messages with a log level smaller + than this setting will be printed to the console. + ''; + }; + + boot.vesa = mkOption { + type = types.bool; + default = false; + description = lib.mdDoc '' + (Deprecated) This option, if set, activates the VESA 800x600 video + mode on boot and disables kernel modesetting. It is equivalent to + specifying `[ "vga=0x317" "nomodeset" ]` in the + {option}`boot.kernelParams` option. This option is + deprecated as of 2020: Xorg now works better with modesetting, and + you might want a different VESA vga setting, anyway. + ''; + }; + + boot.extraModulePackages = mkOption { + type = types.listOf types.package; + default = []; + example = literalExpression "[ config.boot.kernelPackages.nvidia_x11 ]"; + description = lib.mdDoc "A list of additional packages supplying kernel modules."; + }; + + boot.kernelModules = mkOption { + type = types.listOf types.str; + default = []; + description = lib.mdDoc '' + The set of kernel modules to be loaded in the second stage of + the boot process. Note that modules that are needed to + mount the root file system should be added to + {option}`boot.initrd.availableKernelModules` or + {option}`boot.initrd.kernelModules`. + ''; + }; + + boot.initrd.availableKernelModules = mkOption { + type = types.listOf types.str; + default = []; + example = [ "sata_nv" "ext3" ]; + description = lib.mdDoc '' + The set of kernel modules in the initial ramdisk used during the + boot process. This set must include all modules necessary for + mounting the root device. That is, it should include modules + for the physical device (e.g., SCSI drivers) and for the file + system (e.g., ext3). The set specified here is automatically + closed under the module dependency relation, i.e., all + dependencies of the modules list here are included + automatically. The modules listed here are available in the + initrd, but are only loaded on demand (e.g., the ext3 module is + loaded automatically when an ext3 filesystem is mounted, and + modules for PCI devices are loaded when they match the PCI ID + of a device in your system). To force a module to be loaded, + include it in {option}`boot.initrd.kernelModules`. + ''; + }; + + boot.initrd.kernelModules = mkOption { + type = types.listOf types.str; + default = []; + description = lib.mdDoc "List of modules that are always loaded by the initrd."; + }; + + boot.initrd.includeDefaultModules = mkOption { + type = types.bool; + default = true; + description = lib.mdDoc '' + This option, if set, adds a collection of default kernel modules + to {option}`boot.initrd.availableKernelModules` and + {option}`boot.initrd.kernelModules`. + ''; + }; + + system.modulesTree = mkOption { + type = types.listOf types.path; + internal = true; + default = []; + description = lib.mdDoc '' + Tree of kernel modules. This includes the kernel, plus modules + built outside of the kernel. Combine these into a single tree of + symlinks because modprobe only supports one directory. + ''; + # Convert the list of path to only one path. + apply = pkgs.aggregateModules; + }; + + system.requiredKernelConfig = mkOption { + default = []; + example = literalExpression '' + with config.lib.kernelConfig; [ + (isYes "MODULES") + (isEnabled "FB_CON_DECOR") + (isEnabled "BLK_DEV_INITRD") + ] + ''; + internal = true; + type = types.listOf types.attrs; + description = lib.mdDoc '' + This option allows modules to specify the kernel config options that + must be set (or unset) for the module to work. Please use the + lib.kernelConfig functions to build list elements. + ''; + }; + + }; + + + ###### implementation + + config = mkMerge + [ (mkIf config.boot.initrd.enable { + boot.initrd.availableKernelModules = + optionals config.boot.initrd.includeDefaultModules ([ + # 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" + + # NVMe + "nvme" + + # 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" "hid_microsoft" "hid_cherry" + + ] ++ optionals pkgs.stdenv.hostPlatform.isx86 [ + # Misc. x86 keyboard stuff. + "pcips2" "atkbd" "i8042" + + # x86 RTC needed by the stage 2 init script. + "rtc_cmos" + ]); + + boot.initrd.kernelModules = + optionals config.boot.initrd.includeDefaultModules [ + # For LVM. + "dm_mod" + ]; + }) + + (mkIf config.boot.kernel.enable { + system.build = { inherit kernel; }; + + system.modulesTree = [ kernel ] ++ config.boot.extraModulePackages; + + # Not required for, e.g., containers as they don't have their own kernel or initrd. + # They boot directly into stage 2. + system.systemBuilderArgs.kernelParams = config.boot.kernelParams; + system.systemBuilderCommands = + let + kernelPath = "${config.boot.kernelPackages.kernel}/" + + "${config.system.boot.loader.kernelFile}"; + initrdPath = "${config.system.build.initialRamdisk}/" + + "${config.system.boot.loader.initrdFile}"; + in + '' + if [ ! -f ${kernelPath} ]; then + echo "The bootloader cannot find the proper kernel image." + echo "(Expecting ${kernelPath})" + false + fi + + ln -s ${kernelPath} $out/kernel + ln -s ${config.system.modulesTree} $out/kernel-modules + ${optionalString (config.hardware.deviceTree.package != null) '' + ln -s ${config.hardware.deviceTree.package} $out/dtbs + ''} + + echo -n "$kernelParams" > $out/kernel-params + + ln -s ${initrdPath} $out/initrd + + ln -s ${config.system.build.initialRamdiskSecretAppender}/bin/append-initrd-secrets $out + + ln -s ${config.hardware.firmware}/lib/firmware $out/firmware + ''; + + # 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" ]; + + # 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; + }; + + 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"; + }; + }; + + 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/nixpkgs/nixos/modules/system/boot/kernel_config.nix b/nixpkgs/nixos/modules/system/boot/kernel_config.nix new file mode 100644 index 000000000000..e618070f0dc3 --- /dev/null +++ b/nixpkgs/nixos/modules/system/boot/kernel_config.nix @@ -0,0 +1,116 @@ +{ lib, config, ... }: + +with lib; +let + mergeFalseByDefault = locs: defs: + if defs == [] then abort "This case should never happen." + else if any (x: x == false) (getValues defs) then false + else true; + + kernelItem = types.submodule { + options = { + tristate = mkOption { + type = types.enum [ "y" "m" "n" null ]; + default = null; + internal = true; + visible = true; + description = lib.mdDoc '' + Use this field for tristate kernel options expecting a "y" or "m" or "n". + ''; + }; + + freeform = mkOption { + type = types.nullOr types.str // { + merge = mergeEqualOption; + }; + default = null; + example = ''MMC_BLOCK_MINORS.freeform = "32";''; + description = lib.mdDoc '' + Freeform description of a kernel configuration item value. + ''; + }; + + optional = mkOption { + type = types.bool // { merge = mergeFalseByDefault; }; + default = false; + description = lib.mdDoc '' + Whether option should generate a failure when unused. + Upon merging values, mandatory wins over optional. + ''; + }; + }; + }; + + mkValue = with lib; val: + let + isNumber = c: elem c ["0" "1" "2" "3" "4" "5" "6" "7" "8" "9"]; + + in + if (val == "") then "\"\"" + else if val == "y" || val == "m" || val == "n" then val + else if all isNumber (stringToCharacters val) then val + else if substring 0 2 val == "0x" then val + else val; # FIXME: fix quoting one day + + + # generate nix intermediate kernel config file of the form + # + # VIRTIO_MMIO m + # VIRTIO_BLK y + # VIRTIO_CONSOLE n + # NET_9P_VIRTIO? y + # + # Borrowed from copumpkin https://github.com/NixOS/nixpkgs/pull/12158 + # returns a string, expr should be an attribute set + # Use mkValuePreprocess to preprocess option values, aka mark 'modules' as 'yes' or vice-versa + # use the identity if you don't want to override the configured values + generateNixKConf = exprs: + let + mkConfigLine = key: item: + let + val = if item.freeform != null then item.freeform else item.tristate; + in + optionalString (val != null) + (if (item.optional) + then "${key}? ${mkValue val}\n" + else "${key} ${mkValue val}\n"); + + mkConf = cfg: concatStrings (mapAttrsToList mkConfigLine cfg); + in mkConf exprs; + +in +{ + + options = { + + intermediateNixConfig = mkOption { + readOnly = true; + type = types.lines; + example = '' + USB? y + DEBUG n + ''; + description = lib.mdDoc '' + The result of converting the structured kernel configuration in settings + to an intermediate string that can be parsed by generate-config.pl to + answer the kernel `make defconfig`. + ''; + }; + + settings = mkOption { + type = types.attrsOf kernelItem; + example = literalExpression '' with lib.kernel; { + "9P_NET" = yes; + USB = option yes; + MMC_BLOCK_MINORS = freeform "32"; + }''; + description = lib.mdDoc '' + Structured kernel configuration. + ''; + }; + }; + + config = { + intermediateNixConfig = generateNixKConf config.settings; + }; +} diff --git a/nixpkgs/nixos/modules/system/boot/kexec.nix b/nixpkgs/nixos/modules/system/boot/kexec.nix new file mode 100644 index 000000000000..02c2713ede11 --- /dev/null +++ b/nixpkgs/nixos/modules/system/boot/kexec.nix @@ -0,0 +1,32 @@ +{ pkgs, lib, ... }: + +{ + config = lib.mkIf (lib.meta.availableOn pkgs.stdenv.hostPlatform pkgs.kexec-tools) { + environment.systemPackages = [ pkgs.kexec-tools ]; + + systemd.services.prepare-kexec = + { description = "Preparation for kexec"; + wantedBy = [ "kexec.target" ]; + before = [ "systemd-kexec.service" ]; + unitConfig.DefaultDependencies = false; + serviceConfig.Type = "oneshot"; + path = [ pkgs.kexec-tools ]; + script = + '' + # Don't load the current system profile if we already have a kernel loaded + if [[ 1 = "$(</sys/kernel/kexec_loaded)" ]] ; then + echo "kexec kernel has already been loaded, prepare-kexec skipped" + exit 0 + fi + + p=$(readlink -f /nix/var/nix/profiles/system) + if ! [[ -d $p ]]; then + echo "Could not find system profile for prepare-kexec" + exit 1 + fi + echo "Loading NixOS system via kexec." + exec kexec --load $p/kernel --initrd=$p/initrd --append="$(cat $p/kernel-params) init=$p/init" + ''; + }; + }; +} diff --git a/nixpkgs/nixos/modules/system/boot/loader/efi.nix b/nixpkgs/nixos/modules/system/boot/loader/efi.nix new file mode 100644 index 000000000000..2661f362249d --- /dev/null +++ b/nixpkgs/nixos/modules/system/boot/loader/efi.nix @@ -0,0 +1,20 @@ +{ lib, ... }: + +with lib; + +{ + options.boot.loader.efi = { + + canTouchEfiVariables = mkOption { + default = false; + type = types.bool; + description = lib.mdDoc "Whether the installation process is allowed to modify EFI boot variables."; + }; + + efiSysMountPoint = mkOption { + default = "/boot"; + type = types.str; + description = lib.mdDoc "Where the EFI System Partition is mounted."; + }; + }; +} diff --git a/nixpkgs/nixos/modules/system/boot/loader/external/external.md b/nixpkgs/nixos/modules/system/boot/loader/external/external.md new file mode 100644 index 000000000000..4f5b559dfc40 --- /dev/null +++ b/nixpkgs/nixos/modules/system/boot/loader/external/external.md @@ -0,0 +1,26 @@ +# External Bootloader Backends {#sec-bootloader-external} + +NixOS has support for several bootloader backends by default: systemd-boot, grub, uboot, etc. +The built-in bootloader backend support is generic and supports most use cases. +Some users may prefer to create advanced workflows around managing the bootloader and bootable entries. + +You can replace the built-in bootloader support with your own tooling using the "external" bootloader option. + +Imagine you have created a new package called FooBoot. +FooBoot provides a program at `${pkgs.fooboot}/bin/fooboot-install` which takes the system closure's path as its only argument and configures the system's bootloader. + +You can enable FooBoot like this: + +```nix +{ pkgs, ... }: { + boot.loader.external = { + enable = true; + installHook = "${pkgs.fooboot}/bin/fooboot-install"; + }; +} +``` + +## Developing Custom Bootloader Backends {#sec-bootloader-external-developing} + +Bootloaders should use [RFC-0125](https://github.com/NixOS/rfcs/pull/125)'s Bootspec format and synthesis tools to identify the key properties for bootable system generations. + diff --git a/nixpkgs/nixos/modules/system/boot/loader/external/external.nix b/nixpkgs/nixos/modules/system/boot/loader/external/external.nix new file mode 100644 index 000000000000..78982356a9ea --- /dev/null +++ b/nixpkgs/nixos/modules/system/boot/loader/external/external.nix @@ -0,0 +1,36 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + cfg = config.boot.loader.external; +in +{ + meta = { + maintainers = with maintainers; [ cole-h grahamc raitobezarius ]; + doc = ./external.md; + }; + + options.boot.loader.external = { + enable = mkEnableOption (lib.mdDoc "using an external tool to install your bootloader"); + + installHook = mkOption { + type = with types; path; + description = lib.mdDoc '' + The full path to a program of your choosing which performs the bootloader installation process. + + The program will be called with an argument pointing to the output of the system's toplevel. + ''; + }; + }; + + config = mkIf cfg.enable { + boot.loader = { + grub.enable = mkDefault false; + systemd-boot.enable = mkDefault false; + supportsInitrdSecrets = mkDefault false; + }; + + system.build.installBootLoader = cfg.installHook; + }; +} diff --git a/nixpkgs/nixos/modules/system/boot/loader/generations-dir/generations-dir-builder.sh b/nixpkgs/nixos/modules/system/boot/loader/generations-dir/generations-dir-builder.sh new file mode 100644 index 000000000000..8ae23dc988c2 --- /dev/null +++ b/nixpkgs/nixos/modules/system/boot/loader/generations-dir/generations-dir-builder.sh @@ -0,0 +1,106 @@ +#! @bash@/bin/sh -e + +shopt -s nullglob + +export PATH=/empty +for i in @path@; do PATH=$PATH:$i/bin; done + +default=$1 +if test -z "$1"; then + echo "Syntax: generations-dir-builder.sh <DEFAULT-CONFIG>" + exit 1 +fi + +echo "updating the boot generations directory..." + +mkdir -p /boot + +rm -Rf /boot/system* || true + +target=/boot/grub/menu.lst +tmp=$target.tmp + +# Convert a path to a file in the Nix store such as +# /nix/store/<hash>-<name>/file to <hash>-<name>-<file>. +cleanName() { + local path="$1" + echo "$path" | sed 's|^/nix/store/||' | sed 's|/|-|g' +} + +# Copy a file from the Nix store to /boot/kernels. +declare -A filesCopied + +copyToKernelsDir() { + local src="$1" + local dst="/boot/kernels/$(cleanName $src)" + # Don't copy the file if $dst already exists. This means that we + # have to create $dst atomically to prevent partially copied + # kernels or initrd if this script is ever interrupted. + if ! test -e $dst; then + local dstTmp=$dst.tmp.$$ + cp $src $dstTmp + mv $dstTmp $dst + fi + filesCopied[$dst]=1 + result=$dst +} + + +# Copy its kernel and initrd to /boot/kernels. +addEntry() { + local path="$1" + local generation="$2" + local outdir=/boot/system-$generation + + if ! test -e $path/kernel -a -e $path/initrd; then + return + fi + + local kernel=$(readlink -f $path/kernel) + local initrd=$(readlink -f $path/initrd) + + if test -n "@copyKernels@"; then + copyToKernelsDir $kernel; kernel=$result + copyToKernelsDir $initrd; initrd=$result + fi + + mkdir -p $outdir + ln -sf $(readlink -f $path) $outdir/system + ln -sf $(readlink -f $path/init) $outdir/init + ln -sf $initrd $outdir/initrd + ln -sf $kernel $outdir/kernel + + if test $(readlink -f "$path") = "$default"; then + cp "$kernel" /boot/nixos-kernel + cp "$initrd" /boot/nixos-initrd + cp "$(readlink -f "$path/init")" /boot/nixos-init + + mkdir -p /boot/default + # ln -sfT: overrides target even if it exists. + ln -sfT $(readlink -f $path) /boot/default/system + ln -sfT $(readlink -f $path/init) /boot/default/init + ln -sfT $initrd /boot/default/initrd + ln -sfT $kernel /boot/default/kernel + fi +} + +if test -n "@copyKernels@"; then + mkdir -p /boot/kernels +fi + +# Add all generations of the system profile to the menu, in reverse +# (most recent to least recent) order. +for generation in $( + (cd /nix/var/nix/profiles && ls -d system-*-link) \ + | sed 's/system-\([0-9]\+\)-link/\1/' \ + | sort -n -r); do + link=/nix/var/nix/profiles/system-$generation-link + addEntry $link $generation +done + +# Remove obsolete files from /boot/kernels. +for fn in /boot/kernels/*; do + if ! test "${filesCopied[$fn]}" = 1; then + rm -vf -- "$fn" + fi +done diff --git a/nixpkgs/nixos/modules/system/boot/loader/generations-dir/generations-dir.nix b/nixpkgs/nixos/modules/system/boot/loader/generations-dir/generations-dir.nix new file mode 100644 index 000000000000..5ace5dd06fd4 --- /dev/null +++ b/nixpkgs/nixos/modules/system/boot/loader/generations-dir/generations-dir.nix @@ -0,0 +1,62 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + + generationsDirBuilder = pkgs.substituteAll { + src = ./generations-dir-builder.sh; + isExecutable = true; + inherit (pkgs) bash; + path = [pkgs.coreutils pkgs.gnused pkgs.gnugrep]; + inherit (config.boot.loader.generationsDir) copyKernels; + }; + +in + +{ + options = { + + boot.loader.generationsDir = { + + enable = mkOption { + default = false; + type = types.bool; + description = lib.mdDoc '' + Whether to create symlinks to the system generations under + `/boot`. When enabled, + `/boot/default/kernel`, + `/boot/default/initrd`, etc., are updated to + point to the current generation's kernel image, initial RAM + disk, and other bootstrap files. + + This optional is not necessary with boot loaders such as GNU GRUB + for which the menu is updated to point to the latest bootstrap + files. However, it is needed for U-Boot on platforms where the + boot command line is stored in flash memory rather than in a + menu file. + ''; + }; + + copyKernels = mkOption { + default = false; + type = types.bool; + description = lib.mdDoc '' + Whether copy the necessary boot files into /boot, so + /nix/store is not needed by the boot loader. + ''; + }; + + }; + + }; + + + config = mkIf config.boot.loader.generationsDir.enable { + + system.build.installBootLoader = generationsDirBuilder; + system.boot.loader.id = "generationsDir"; + system.boot.loader.kernelFile = pkgs.stdenv.hostPlatform.linux-kernel.target; + + }; +} diff --git a/nixpkgs/nixos/modules/system/boot/loader/generic-extlinux-compatible/default.nix b/nixpkgs/nixos/modules/system/boot/loader/generic-extlinux-compatible/default.nix new file mode 100644 index 000000000000..13df60907116 --- /dev/null +++ b/nixpkgs/nixos/modules/system/boot/loader/generic-extlinux-compatible/default.nix @@ -0,0 +1,82 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + blCfg = config.boot.loader; + dtCfg = config.hardware.deviceTree; + cfg = blCfg.generic-extlinux-compatible; + + timeoutStr = if blCfg.timeout == null then "-1" else toString blCfg.timeout; + + # The builder used to write during system activation + builder = import ./extlinux-conf-builder.nix { inherit pkgs; }; + # The builder exposed in populateCmd, which runs on the build architecture + populateBuilder = import ./extlinux-conf-builder.nix { pkgs = pkgs.buildPackages; }; +in +{ + options = { + boot.loader.generic-extlinux-compatible = { + enable = mkOption { + default = false; + type = types.bool; + description = lib.mdDoc '' + Whether to generate an extlinux-compatible configuration file + under `/boot/extlinux.conf`. For instance, + U-Boot's generic distro boot support uses this file format. + + See [U-boot's documentation](https://u-boot.readthedocs.io/en/latest/develop/distro.html) + for more information. + ''; + }; + + useGenerationDeviceTree = mkOption { + default = true; + type = types.bool; + description = lib.mdDoc '' + Whether to generate Device Tree-related directives in the + extlinux configuration. + + When enabled, the bootloader will attempt to load the device + tree binaries from the generation's kernel. + + Note that this affects all generations, regardless of the + setting value used in their configurations. + ''; + }; + + configurationLimit = mkOption { + default = 20; + example = 10; + type = types.int; + description = lib.mdDoc '' + Maximum number of configurations in the boot menu. + ''; + }; + + populateCmd = mkOption { + type = types.str; + readOnly = true; + description = lib.mdDoc '' + Contains the builder command used to populate an image, + honoring all options except the `-c <path-to-default-configuration>` + argument. + Useful to have for sdImage.populateRootCommands + ''; + }; + + }; + }; + + config = let + builderArgs = "-g ${toString cfg.configurationLimit} -t ${timeoutStr}" + + lib.optionalString (dtCfg.name != null) " -n ${dtCfg.name}" + + lib.optionalString (!cfg.useGenerationDeviceTree) " -r"; + in + mkIf cfg.enable { + system.build.installBootLoader = "${builder} ${builderArgs} -c"; + system.boot.loader.id = "generic-extlinux-compatible"; + + boot.loader.generic-extlinux-compatible.populateCmd = "${populateBuilder} ${builderArgs}"; + }; +} diff --git a/nixpkgs/nixos/modules/system/boot/loader/generic-extlinux-compatible/extlinux-conf-builder.nix b/nixpkgs/nixos/modules/system/boot/loader/generic-extlinux-compatible/extlinux-conf-builder.nix new file mode 100644 index 000000000000..576a07c1d272 --- /dev/null +++ b/nixpkgs/nixos/modules/system/boot/loader/generic-extlinux-compatible/extlinux-conf-builder.nix @@ -0,0 +1,8 @@ +{ pkgs }: + +pkgs.substituteAll { + src = ./extlinux-conf-builder.sh; + isExecutable = true; + path = [pkgs.coreutils pkgs.gnused pkgs.gnugrep]; + inherit (pkgs) bash; +} diff --git a/nixpkgs/nixos/modules/system/boot/loader/generic-extlinux-compatible/extlinux-conf-builder.sh b/nixpkgs/nixos/modules/system/boot/loader/generic-extlinux-compatible/extlinux-conf-builder.sh new file mode 100644 index 000000000000..1a0da0050291 --- /dev/null +++ b/nixpkgs/nixos/modules/system/boot/loader/generic-extlinux-compatible/extlinux-conf-builder.sh @@ -0,0 +1,157 @@ +#! @bash@/bin/sh -e + +shopt -s nullglob + +export PATH=/empty +for i in @path@; do PATH=$PATH:$i/bin; done + +usage() { + echo "usage: $0 -t <timeout> -c <path-to-default-configuration> [-d <boot-dir>] [-g <num-generations>] [-n <dtbName>] [-r]" >&2 + exit 1 +} + +timeout= # Timeout in centiseconds +default= # Default configuration +target=/boot # Target directory +numGenerations=0 # Number of other generations to include in the menu + +while getopts "t:c:d:g:n:r" opt; do + case "$opt" in + t) # U-Boot interprets '0' as infinite and negative as instant boot + if [ "$OPTARG" -lt 0 ]; then + timeout=0 + elif [ "$OPTARG" = 0 ]; then + timeout=-10 + else + timeout=$((OPTARG * 10)) + fi + ;; + c) default="$OPTARG" ;; + d) target="$OPTARG" ;; + g) numGenerations="$OPTARG" ;; + n) dtbName="$OPTARG" ;; + r) noDeviceTree=1 ;; + \?) usage ;; + esac +done + +[ "$timeout" = "" -o "$default" = "" ] && usage + +mkdir -p $target/nixos +mkdir -p $target/extlinux + +# Convert a path to a file in the Nix store such as +# /nix/store/<hash>-<name>/file to <hash>-<name>-<file>. +cleanName() { + local path="$1" + echo "$path" | sed 's|^/nix/store/||' | sed 's|/|-|g' +} + +# Copy a file from the Nix store to $target/nixos. +declare -A filesCopied + +copyToKernelsDir() { + local src=$(readlink -f "$1") + local dst="$target/nixos/$(cleanName $src)" + # Don't copy the file if $dst already exists. This means that we + # have to create $dst atomically to prevent partially copied + # kernels or initrd if this script is ever interrupted. + if ! test -e $dst; then + local dstTmp=$dst.tmp.$$ + cp -r $src $dstTmp + mv $dstTmp $dst + fi + filesCopied[$dst]=1 + result=$dst +} + +# Copy its kernel, initrd and dtbs to $target/nixos, and echo out an +# extlinux menu entry +addEntry() { + local path=$(readlink -f "$1") + local tag="$2" # Generation number or 'default' + + if ! test -e $path/kernel -a -e $path/initrd; then + return + fi + + copyToKernelsDir "$path/kernel"; kernel=$result + copyToKernelsDir "$path/initrd"; initrd=$result + dtbDir=$(readlink -m "$path/dtbs") + if [ -e "$dtbDir" ]; then + copyToKernelsDir "$dtbDir"; dtbs=$result + fi + + timestampEpoch=$(stat -L -c '%Z' $path) + + timestamp=$(date "+%Y-%m-%d %H:%M" -d @$timestampEpoch) + nixosLabel="$(cat $path/nixos-version)" + extraParams="$(cat $path/kernel-params)" + + echo + echo "LABEL nixos-$tag" + if [ "$tag" = "default" ]; then + echo " MENU LABEL NixOS - Default" + else + echo " MENU LABEL NixOS - Configuration $tag ($timestamp - $nixosLabel)" + fi + echo " LINUX ../nixos/$(basename $kernel)" + echo " INITRD ../nixos/$(basename $initrd)" + echo " APPEND init=$path/init $extraParams" + + if [ -n "$noDeviceTree" ]; then + return + fi + + if [ -d "$dtbDir" ]; then + # if a dtbName was specified explicitly, use that, else use FDTDIR + if [ -n "$dtbName" ]; then + echo " FDT ../nixos/$(basename $dtbs)/${dtbName}" + else + echo " FDTDIR ../nixos/$(basename $dtbs)" + fi + else + if [ -n "$dtbName" ]; then + echo "Explicitly requested dtbName $dtbName, but there's no FDTDIR - bailing out." >&2 + exit 1 + fi + fi +} + +tmpFile="$target/extlinux/extlinux.conf.tmp.$$" + +cat > $tmpFile <<EOF +# Generated file, all changes will be lost on nixos-rebuild! + +# Change this to e.g. nixos-42 to temporarily boot to an older configuration. +DEFAULT nixos-default + +MENU TITLE ------------------------------------------------------------ +TIMEOUT $timeout +EOF + +addEntry $default default >> $tmpFile + +if [ "$numGenerations" -gt 0 ]; then + # Add up to $numGenerations generations of the system profile to the menu, + # in reverse (most recent to least recent) order. + for generation in $( + (cd /nix/var/nix/profiles && ls -d system-*-link) \ + | sed 's/system-\([0-9]\+\)-link/\1/' \ + | sort -n -r \ + | head -n $numGenerations); do + link=/nix/var/nix/profiles/system-$generation-link + addEntry $link $generation + done >> $tmpFile +fi + +mv -f $tmpFile $target/extlinux/extlinux.conf + +# Remove obsolete files from $target/nixos. +for fn in $target/nixos/*; do + if ! test "${filesCopied[$fn]}" = 1; then + echo "Removing no longer needed boot file: $fn" + chmod +w -- "$fn" + rm -rf -- "$fn" + fi +done diff --git a/nixpkgs/nixos/modules/system/boot/loader/grub/grub.nix b/nixpkgs/nixos/modules/system/boot/loader/grub/grub.nix new file mode 100644 index 000000000000..0556c875241a --- /dev/null +++ b/nixpkgs/nixos/modules/system/boot/loader/grub/grub.nix @@ -0,0 +1,844 @@ +{ config, options, lib, pkgs, ... }: + +let + inherit (lib) + all + concatMap + concatMapStrings + concatStrings + concatStringsSep + escapeShellArg + flip + foldr + forEach + hasPrefix + mapAttrsToList + literalExpression + makeBinPath + mkDefault + mkIf + mkMerge + mkOption + mkRemovedOptionModule + mkRenamedOptionModule + optional + optionals + optionalString + replaceStrings + types + ; + + cfg = config.boot.loader.grub; + + efi = config.boot.loader.efi; + + grubPkgs = + # Package set of targeted architecture + if cfg.forcei686 then pkgs.pkgsi686Linux else pkgs; + + realGrub = if cfg.zfsSupport then grubPkgs.grub2.override { zfsSupport = true; zfs = cfg.zfsPackage; } + else grubPkgs.grub2; + + grub = + # Don't include GRUB if we're only generating a GRUB menu (e.g., + # in EC2 instances). + if cfg.devices == ["nodev"] + then null + else realGrub; + + grubEfi = + if cfg.efiSupport + then realGrub.override { efiSupport = cfg.efiSupport; } + else null; + + f = x: optionalString (x != null) ("" + x); + + grubConfig = args: + let + efiSysMountPoint = if args.efiSysMountPoint == null then args.path else args.efiSysMountPoint; + efiSysMountPoint' = replaceStrings [ "/" ] [ "-" ] efiSysMountPoint; + in + pkgs.writeText "grub-config.xml" (builtins.toXML + { splashImage = f cfg.splashImage; + splashMode = f cfg.splashMode; + backgroundColor = f cfg.backgroundColor; + entryOptions = f cfg.entryOptions; + subEntryOptions = f cfg.subEntryOptions; + # PC platforms (like x86_64-linux) have a non-EFI target (`grubTarget`), but other platforms + # (like aarch64-linux) have an undefined `grubTarget`. Avoid providing the path to a non-EFI + # GRUB on those platforms. + grub = f (if (grub.grubTarget or "") != "" then grub else ""); + grubTarget = f (grub.grubTarget or ""); + shell = "${pkgs.runtimeShell}"; + fullName = lib.getName realGrub; + fullVersion = lib.getVersion realGrub; + grubEfi = f grubEfi; + grubTargetEfi = optionalString cfg.efiSupport (f (grubEfi.grubTarget or "")); + bootPath = args.path; + storePath = config.boot.loader.grub.storePath; + bootloaderId = if args.efiBootloaderId == null then "${config.system.nixos.distroName}${efiSysMountPoint'}" else args.efiBootloaderId; + timeout = if config.boot.loader.timeout == null then -1 else config.boot.loader.timeout; + theme = f cfg.theme; + inherit efiSysMountPoint; + inherit (args) devices; + inherit (efi) canTouchEfiVariables; + inherit (cfg) + extraConfig extraPerEntryConfig extraEntries forceInstall useOSProber + extraGrubInstallArgs + extraEntriesBeforeNixOS extraPrepareConfig configurationLimit copyKernels + default fsIdentifier efiSupport efiInstallAsRemovable gfxmodeEfi gfxmodeBios gfxpayloadEfi gfxpayloadBios + users + timeoutStyle + ; + path = with pkgs; makeBinPath ( + [ coreutils gnused gnugrep findutils diffutils btrfs-progs util-linux mdadm ] + ++ optional cfg.efiSupport efibootmgr + ++ optionals cfg.useOSProber [ busybox os-prober ]); + font = lib.optionalString (cfg.font != null) ( + if lib.last (lib.splitString "." cfg.font) == "pf2" + then cfg.font + else "${convertedFont}"); + }); + + bootDeviceCounters = foldr (device: attr: attr // { ${device} = (attr.${device} or 0) + 1; }) {} + (concatMap (args: args.devices) cfg.mirroredBoots); + + convertedFont = (pkgs.runCommand "grub-font-converted.pf2" {} + (builtins.concatStringsSep " " + ([ "${realGrub}/bin/grub-mkfont" + cfg.font + "--output" "$out" + ] ++ (optional (cfg.fontSize!=null) "--size ${toString cfg.fontSize}"))) + ); + + defaultSplash = pkgs.nixos-artwork.wallpapers.simple-dark-gray-bootloader.gnomeFilePath; +in + +{ + + ###### interface + + options = { + + boot.loader.grub = { + + enable = mkOption { + default = !config.boot.isContainer; + defaultText = literalExpression "!config.boot.isContainer"; + type = types.bool; + description = lib.mdDoc '' + Whether to enable the GNU GRUB boot loader. + ''; + }; + + version = mkOption { + visible = false; + type = types.int; + }; + + device = mkOption { + default = ""; + example = "/dev/disk/by-id/wwn-0x500001234567890a"; + type = types.str; + description = lib.mdDoc '' + The device on which the GRUB boot loader will be installed. + The special value `nodev` means that a GRUB + boot menu will be generated, but GRUB itself will not + actually be installed. To install GRUB on multiple devices, + use `boot.loader.grub.devices`. + ''; + }; + + devices = mkOption { + default = []; + example = [ "/dev/disk/by-id/wwn-0x500001234567890a" ]; + type = types.listOf types.str; + description = lib.mdDoc '' + The devices on which the boot loader, GRUB, will be + installed. Can be used instead of `device` to + install GRUB onto multiple devices. + ''; + }; + + users = mkOption { + default = {}; + example = { + root = { hashedPasswordFile = "/path/to/file"; }; + }; + description = lib.mdDoc '' + User accounts for GRUB. When specified, the GRUB command line and + all boot options except the default are password-protected. + All passwords and hashes provided will be stored in /boot/grub/grub.cfg, + and will be visible to any local user who can read this file. Additionally, + any passwords and hashes provided directly in a Nix configuration + (as opposed to external files) will be copied into the Nix store, and + will be visible to all local users. + ''; + type = types.attrsOf (types.submodule { + options = { + hashedPasswordFile = mkOption { + example = "/path/to/file"; + default = null; + type = with types; uniq (nullOr str); + description = lib.mdDoc '' + Specifies the path to a file containing the password hash + for the account, generated with grub-mkpasswd-pbkdf2. + This hash will be stored in /boot/grub/grub.cfg, and will + be visible to any local user who can read this file. + ''; + }; + hashedPassword = mkOption { + example = "grub.pbkdf2.sha512.10000.674DFFDEF76E13EA...2CC972B102CF4355"; + default = null; + type = with types; uniq (nullOr str); + description = lib.mdDoc '' + Specifies the password hash for the account, + generated with grub-mkpasswd-pbkdf2. + This hash will be copied to the Nix store, and will be visible to all local users. + ''; + }; + passwordFile = mkOption { + example = "/path/to/file"; + default = null; + type = with types; uniq (nullOr str); + description = lib.mdDoc '' + Specifies the path to a file containing the + clear text password for the account. + This password will be stored in /boot/grub/grub.cfg, and will + be visible to any local user who can read this file. + ''; + }; + password = mkOption { + example = "Pa$$w0rd!"; + default = null; + type = with types; uniq (nullOr str); + description = lib.mdDoc '' + Specifies the clear text password for the account. + This password will be copied to the Nix store, and will be visible to all local users. + ''; + }; + }; + }); + }; + + mirroredBoots = mkOption { + default = [ ]; + example = [ + { path = "/boot1"; devices = [ "/dev/disk/by-id/wwn-0x500001234567890a" ]; } + { path = "/boot2"; devices = [ "/dev/disk/by-id/wwn-0x500009876543210a" ]; } + ]; + description = lib.mdDoc '' + Mirror the boot configuration to multiple partitions and install grub + to the respective devices corresponding to those partitions. + ''; + + type = with types; listOf (submodule { + options = { + + path = mkOption { + example = "/boot1"; + type = types.str; + description = lib.mdDoc '' + The path to the boot directory where GRUB will be written. Generally + this boot path should double as an EFI path. + ''; + }; + + efiSysMountPoint = mkOption { + default = null; + example = "/boot1/efi"; + type = types.nullOr types.str; + description = lib.mdDoc '' + The path to the efi system mount point. Usually this is the same + partition as the above path and can be left as null. + ''; + }; + + efiBootloaderId = mkOption { + default = null; + example = "NixOS-fsid"; + type = types.nullOr types.str; + description = lib.mdDoc '' + The id of the bootloader to store in efi nvram. + The default is to name it NixOS and append the path or efiSysMountPoint. + This is only used if `boot.loader.efi.canTouchEfiVariables` is true. + ''; + }; + + devices = mkOption { + default = [ ]; + example = [ "/dev/disk/by-id/wwn-0x500001234567890a" "/dev/disk/by-id/wwn-0x500009876543210a" ]; + type = types.listOf types.str; + description = lib.mdDoc '' + The path to the devices which will have the GRUB MBR written. + Note these are typically device paths and not paths to partitions. + ''; + }; + + }; + }); + }; + + configurationName = mkOption { + default = ""; + example = "Stable 2.6.21"; + type = types.str; + description = lib.mdDoc '' + GRUB entry name instead of default. + ''; + }; + + storePath = mkOption { + default = "/nix/store"; + type = types.str; + description = lib.mdDoc '' + Path to the Nix store when looking for kernels at boot. + Only makes sense when copyKernels is false. + ''; + }; + + extraPrepareConfig = mkOption { + default = ""; + type = types.lines; + description = lib.mdDoc '' + Additional bash commands to be run at the script that + prepares the GRUB menu entries. + ''; + }; + + extraConfig = mkOption { + default = ""; + example = '' + serial --unit=0 --speed=115200 --word=8 --parity=no --stop=1 + terminal_input --append serial + terminal_output --append serial + ''; + type = types.lines; + description = lib.mdDoc '' + Additional GRUB commands inserted in the configuration file + just before the menu entries. + ''; + }; + + extraGrubInstallArgs = mkOption { + default = [ ]; + example = [ "--modules=nativedisk ahci pata part_gpt part_msdos diskfilter mdraid1x lvm ext2" ]; + type = types.listOf types.str; + description = lib.mdDoc '' + Additional arguments passed to `grub-install`. + + A use case for this is to build specific GRUB2 modules + directly into the GRUB2 kernel image, so that they are available + and activated even in the `grub rescue` shell. + + They are also necessary when the BIOS/UEFI is bugged and cannot + correctly read large disks (e.g. above 2 TB), so GRUB2's own + `nativedisk` and related modules can be used + to use its own disk drivers. The example shows one such case. + This is also useful for booting from USB. + See the + [ + GRUB source code + ](https://git.savannah.gnu.org/cgit/grub.git/tree/grub-core/commands/nativedisk.c?h=grub-2.04#n326) + for which disk modules are available. + + The list elements are passed directly as `argv` + arguments to the `grub-install` program, in order. + ''; + }; + + extraInstallCommands = mkOption { + default = ""; + example = '' + # the example below generates detached signatures that GRUB can verify + # https://www.gnu.org/software/grub/manual/grub/grub.html#Using-digital-signatures + ''${pkgs.findutils}/bin/find /boot -not -path "/boot/efi/*" -type f -name '*.sig' -delete + old_gpg_home=$GNUPGHOME + export GNUPGHOME="$(mktemp -d)" + ''${pkgs.gnupg}/bin/gpg --import ''${priv_key} > /dev/null 2>&1 + ''${pkgs.findutils}/bin/find /boot -not -path "/boot/efi/*" -type f -exec ''${pkgs.gnupg}/bin/gpg --detach-sign "{}" \; > /dev/null 2>&1 + rm -rf $GNUPGHOME + export GNUPGHOME=$old_gpg_home + ''; + type = types.lines; + description = lib.mdDoc '' + Additional shell commands inserted in the bootloader installer + script after generating menu entries. + ''; + }; + + extraPerEntryConfig = mkOption { + default = ""; + example = "root (hd0)"; + type = types.lines; + description = lib.mdDoc '' + Additional GRUB commands inserted in the configuration file + at the start of each NixOS menu entry. + ''; + }; + + extraEntries = mkOption { + default = ""; + type = types.lines; + example = '' + # GRUB 2 example + menuentry "Windows 7" { + chainloader (hd0,4)+1 + } + + # GRUB 2 with UEFI example, chainloading another distro + menuentry "Fedora" { + set root=(hd1,1) + chainloader /efi/fedora/grubx64.efi + } + ''; + description = lib.mdDoc '' + Any additional entries you want added to the GRUB boot menu. + ''; + }; + + extraEntriesBeforeNixOS = mkOption { + default = false; + type = types.bool; + description = lib.mdDoc '' + Whether extraEntries are included before the default option. + ''; + }; + + extraFiles = mkOption { + type = types.attrsOf types.path; + default = {}; + example = literalExpression '' + { "memtest.bin" = "''${pkgs.memtest86plus}/memtest.bin"; } + ''; + description = lib.mdDoc '' + A set of files to be copied to {file}`/boot`. + Each attribute name denotes the destination file name in + {file}`/boot`, while the corresponding + attribute value specifies the source file. + ''; + }; + + useOSProber = mkOption { + default = false; + type = types.bool; + description = lib.mdDoc '' + If set to true, append entries for other OSs detected by os-prober. + ''; + }; + + splashImage = mkOption { + type = types.nullOr types.path; + example = literalExpression "./my-background.png"; + description = lib.mdDoc '' + Background image used for GRUB. + Set to `null` to run GRUB in text mode. + + ::: {.note} + File must be one of .png, .tga, .jpg, or .jpeg. JPEG images must + not be progressive. + The image will be scaled if necessary to fit the screen. + ::: + ''; + }; + + backgroundColor = mkOption { + type = types.nullOr types.str; + example = "#7EBAE4"; + default = null; + description = lib.mdDoc '' + Background color to be used for GRUB to fill the areas the image isn't filling. + ''; + }; + + timeoutStyle = mkOption { + default = "menu"; + type = types.enum [ "menu" "countdown" "hidden" ]; + description = lib.mdDoc '' + - `menu` shows the menu. + - `countdown` uses a text-mode countdown. + - `hidden` hides GRUB entirely. + + When using a theme, the default value (`menu`) is appropriate for the graphical countdown. + + When attempting to do flicker-free boot, `hidden` should be used. + + See the [GRUB documentation section about `timeout_style`](https://www.gnu.org/software/grub/manual/grub/html_node/timeout.html). + + ::: {.note} + If this option is set to ‘countdown’ or ‘hidden’ [...] and ESC or F4 are pressed, or SHIFT is held down during that time, it will display the menu and wait for input. + ::: + + From: [Simple configuration handling page, under GRUB_TIMEOUT_STYLE](https://www.gnu.org/software/grub/manual/grub/html_node/Simple-configuration.html). + ''; + }; + + entryOptions = mkOption { + default = "--class nixos --unrestricted"; + type = types.nullOr types.str; + description = lib.mdDoc '' + Options applied to the primary NixOS menu entry. + ''; + }; + + subEntryOptions = mkOption { + default = "--class nixos"; + type = types.nullOr types.str; + description = lib.mdDoc '' + Options applied to the secondary NixOS submenu entry. + ''; + }; + + theme = mkOption { + type = types.nullOr types.path; + example = literalExpression "pkgs.nixos-grub2-theme"; + default = null; + description = lib.mdDoc '' + Grub theme to be used. + ''; + }; + + splashMode = mkOption { + type = types.enum [ "normal" "stretch" ]; + default = "stretch"; + description = lib.mdDoc '' + Whether to stretch the image or show the image in the top-left corner unstretched. + ''; + }; + + font = mkOption { + type = types.nullOr types.path; + default = "${realGrub}/share/grub/unicode.pf2"; + defaultText = literalExpression ''"''${pkgs.grub2}/share/grub/unicode.pf2"''; + description = lib.mdDoc '' + Path to a TrueType, OpenType, or pf2 font to be used by Grub. + ''; + }; + + fontSize = mkOption { + type = types.nullOr types.int; + example = 16; + default = null; + description = lib.mdDoc '' + Font size for the grub menu. Ignored unless `font` + is set to a ttf or otf font. + ''; + }; + + gfxmodeEfi = mkOption { + default = "auto"; + example = "1024x768"; + type = types.str; + description = lib.mdDoc '' + The gfxmode to pass to GRUB when loading a graphical boot interface under EFI. + ''; + }; + + gfxmodeBios = mkOption { + default = "1024x768"; + example = "auto"; + type = types.str; + description = lib.mdDoc '' + The gfxmode to pass to GRUB when loading a graphical boot interface under BIOS. + ''; + }; + + gfxpayloadEfi = mkOption { + default = "keep"; + example = "text"; + type = types.str; + description = lib.mdDoc '' + The gfxpayload to pass to GRUB when loading a graphical boot interface under EFI. + ''; + }; + + gfxpayloadBios = mkOption { + default = "text"; + example = "keep"; + type = types.str; + description = lib.mdDoc '' + The gfxpayload to pass to GRUB when loading a graphical boot interface under BIOS. + ''; + }; + + configurationLimit = mkOption { + default = 100; + example = 120; + type = types.int; + description = lib.mdDoc '' + Maximum of configurations in boot menu. GRUB has problems when + there are too many entries. + ''; + }; + + copyKernels = mkOption { + default = false; + type = types.bool; + description = lib.mdDoc '' + Whether the GRUB menu builder should copy kernels and initial + ramdisks to /boot. This is done automatically if /boot is + on a different partition than /. + ''; + }; + + default = mkOption { + default = "0"; + type = types.either types.int types.str; + apply = toString; + description = lib.mdDoc '' + Index of the default menu item to be booted. + Can also be set to "saved", which will make GRUB select + the menu item that was used at the last boot. + ''; + }; + + fsIdentifier = mkOption { + default = "uuid"; + type = types.enum [ "uuid" "label" "provided" ]; + description = lib.mdDoc '' + Determines how GRUB will identify devices when generating the + configuration file. A value of uuid / label signifies that grub + will always resolve the uuid or label of the device before using + it in the configuration. A value of provided means that GRUB will + use the device name as show in {command}`df` or + {command}`mount`. Note, zfs zpools / datasets are ignored + and will always be mounted using their labels. + ''; + }; + + zfsSupport = mkOption { + default = false; + type = types.bool; + description = lib.mdDoc '' + Whether GRUB should be built against libzfs. + ''; + }; + + zfsPackage = mkOption { + type = types.package; + internal = true; + default = pkgs.zfs; + defaultText = literalExpression "pkgs.zfs"; + description = lib.mdDoc '' + Which ZFS package to use if `config.boot.loader.grub.zfsSupport` is true. + ''; + }; + + efiSupport = mkOption { + default = false; + type = types.bool; + description = lib.mdDoc '' + Whether GRUB should be built with EFI support. + ''; + }; + + efiInstallAsRemovable = mkOption { + default = false; + type = types.bool; + description = lib.mdDoc '' + Whether to invoke `grub-install` with + `--removable`. + + Unless you turn this on, GRUB will install itself somewhere in + `boot.loader.efi.efiSysMountPoint` (exactly where + depends on other config variables). If you've set + `boot.loader.efi.canTouchEfiVariables` *AND* you + are currently booted in UEFI mode, then GRUB will use + `efibootmgr` to modify the boot order in the + EFI variables of your firmware to include this location. If you are + *not* booted in UEFI mode at the time GRUB is being installed, the + NVRAM will not be modified, and your system will not find GRUB at + boot time. However, GRUB will still return success so you may miss + the warning that gets printed ("`efibootmgr: EFI variables + are not supported on this system.`"). + + If you turn this feature on, GRUB will install itself in a + special location within `efiSysMountPoint` (namely + `EFI/boot/boot$arch.efi`) which the firmwares + are hardcoded to try first, regardless of NVRAM EFI variables. + + To summarize, turn this on if: + - You are installing NixOS and want it to boot in UEFI mode, + but you are currently booted in legacy mode + - You want to make a drive that will boot regardless of + the NVRAM state of the computer (like a USB "removable" drive) + - You simply dislike the idea of depending on NVRAM + state to make your drive bootable + ''; + }; + + enableCryptodisk = mkOption { + default = false; + type = types.bool; + description = lib.mdDoc '' + Enable support for encrypted partitions. GRUB should automatically + unlock the correct encrypted partition and look for filesystems. + ''; + }; + + forceInstall = mkOption { + default = false; + type = types.bool; + description = lib.mdDoc '' + Whether to try and forcibly install GRUB even if problems are + detected. It is not recommended to enable this unless you know what + you are doing. + ''; + }; + + forcei686 = mkOption { + default = false; + type = types.bool; + description = lib.mdDoc '' + Whether to force the use of a ia32 boot loader on x64 systems. Required + to install and run NixOS on 64bit x86 systems with 32bit (U)EFI. + ''; + }; + + }; + + }; + + + ###### implementation + + config = mkMerge [ + + { boot.loader.grub.splashImage = mkDefault defaultSplash; } + + (mkIf (cfg.splashImage == defaultSplash) { + boot.loader.grub.backgroundColor = mkDefault "#2F302F"; + boot.loader.grub.splashMode = mkDefault "normal"; + }) + + (mkIf cfg.enable { + + boot.loader.grub.devices = optional (cfg.device != "") cfg.device; + + boot.loader.grub.mirroredBoots = optionals (cfg.devices != [ ]) [ + { path = "/boot"; inherit (cfg) devices; inherit (efi) efiSysMountPoint; } + ]; + + boot.loader.supportsInitrdSecrets = true; + + system.systemBuilderArgs.configurationName = cfg.configurationName; + system.systemBuilderCommands = '' + echo -n "$configurationName" > $out/configuration-name + ''; + + system.build.installBootLoader = + let + install-grub-pl = pkgs.substituteAll { + src = ./install-grub.pl; + utillinux = pkgs.util-linux; + btrfsprogs = pkgs.btrfs-progs; + inherit (config.system.nixos) distroName; + }; + perl = pkgs.perl.withPackages (p: with p; [ + FileSlurp FileCopyRecursive + XMLLibXML XMLSAX XMLSAXBase + ListCompare JSON + ]); + in pkgs.writeScript "install-grub.sh" ('' + #!${pkgs.runtimeShell} + set -e + ${optionalString cfg.enableCryptodisk "export GRUB_ENABLE_CRYPTODISK=y"} + '' + flip concatMapStrings cfg.mirroredBoots (args: '' + ${perl}/bin/perl ${install-grub-pl} ${grubConfig args} $@ + '') + cfg.extraInstallCommands); + + system.build.grub = grub; + + # Common attribute for boot loaders so only one of them can be + # set at once. + system.boot.loader.id = "grub"; + + environment.systemPackages = optional (grub != null) grub; + + boot.loader.grub.extraPrepareConfig = + concatStrings (mapAttrsToList (n: v: '' + ${pkgs.coreutils}/bin/install -Dp "${v}" "${efi.efiSysMountPoint}/"${escapeShellArg n} + '') config.boot.loader.grub.extraFiles); + + assertions = [ + { + assertion = cfg.mirroredBoots != [ ]; + message = "You must set the option ‘boot.loader.grub.devices’ or " + + "'boot.loader.grub.mirroredBoots' to make the system bootable."; + } + { + assertion = cfg.efiSupport || all (c: c < 2) (mapAttrsToList (n: c: if n == "nodev" then 0 else c) bootDeviceCounters); + message = "You cannot have duplicated devices in mirroredBoots"; + } + { + assertion = cfg.efiInstallAsRemovable -> cfg.efiSupport; + message = "If you wish to to use boot.loader.grub.efiInstallAsRemovable, then turn on boot.loader.grub.efiSupport"; + } + { + assertion = cfg.efiInstallAsRemovable -> !config.boot.loader.efi.canTouchEfiVariables; + message = "If you wish to to use boot.loader.grub.efiInstallAsRemovable, then turn off boot.loader.efi.canTouchEfiVariables"; + } + { + assertion = !(options.boot.loader.grub.version.isDefined && cfg.version == 1); + message = "Support for version 0.9x of GRUB was removed after being unsupported upstream for around a decade"; + } + ] ++ flip concatMap cfg.mirroredBoots (args: [ + { + assertion = args.devices != [ ]; + message = "A boot path cannot have an empty devices string in ${args.path}"; + } + { + assertion = hasPrefix "/" args.path; + message = "Boot paths must be absolute, not ${args.path}"; + } + { + assertion = if args.efiSysMountPoint == null then true else hasPrefix "/" args.efiSysMountPoint; + message = "EFI paths must be absolute, not ${args.efiSysMountPoint}"; + } + ] ++ forEach args.devices (device: { + assertion = device == "nodev" || hasPrefix "/" device; + message = "GRUB devices must be absolute paths, not ${device} in ${args.path}"; + })); + }) + + (mkIf options.boot.loader.grub.version.isDefined { + warnings = [ '' + The boot.loader.grub.version option does not have any effect anymore, please remove it from your configuration. + '' ]; + }) + ]; + + + imports = + [ (mkRemovedOptionModule [ "boot" "loader" "grub" "bootDevice" ] "") + (mkRenamedOptionModule [ "boot" "copyKernels" ] [ "boot" "loader" "grub" "copyKernels" ]) + (mkRenamedOptionModule [ "boot" "extraGrubEntries" ] [ "boot" "loader" "grub" "extraEntries" ]) + (mkRenamedOptionModule [ "boot" "extraGrubEntriesBeforeNixos" ] [ "boot" "loader" "grub" "extraEntriesBeforeNixOS" ]) + (mkRenamedOptionModule [ "boot" "grubDevice" ] [ "boot" "loader" "grub" "device" ]) + (mkRenamedOptionModule [ "boot" "bootMount" ] [ "boot" "loader" "grub" "bootDevice" ]) + (mkRenamedOptionModule [ "boot" "grubSplashImage" ] [ "boot" "loader" "grub" "splashImage" ]) + (mkRemovedOptionModule [ "boot" "loader" "grub" "trustedBoot" ] '' + Support for Trusted GRUB has been removed, because the project + has been retired upstream. + '') + (mkRemovedOptionModule [ "boot" "loader" "grub" "extraInitrd" ] '' + This option has been replaced with the bootloader agnostic + boot.initrd.secrets option. To migrate to the initrd secrets system, + extract the extraInitrd archive into your main filesystem: + + # zcat /boot/extra_initramfs.gz | cpio -idvmD /etc/secrets/initrd + /path/to/secret1 + /path/to/secret2 + + then replace boot.loader.grub.extraInitrd with boot.initrd.secrets: + + boot.initrd.secrets = { + "/path/to/secret1" = "/etc/secrets/initrd/path/to/secret1"; + "/path/to/secret2" = "/etc/secrets/initrd/path/to/secret2"; + }; + + See the boot.initrd.secrets option documentation for more information. + '') + ]; + +} diff --git a/nixpkgs/nixos/modules/system/boot/loader/grub/install-grub.pl b/nixpkgs/nixos/modules/system/boot/loader/grub/install-grub.pl new file mode 100644 index 000000000000..6f0f62546a01 --- /dev/null +++ b/nixpkgs/nixos/modules/system/boot/loader/grub/install-grub.pl @@ -0,0 +1,805 @@ +use strict; +use warnings; +use Class::Struct; +use XML::LibXML; +use File::Basename; +use File::Path; +use File::stat; +use File::Copy; +use File::Copy::Recursive qw(rcopy pathrm); +use File::Slurp; +use File::Temp; +use JSON; +use File::Find; +require List::Compare; +use POSIX; +use Cwd; + +# system.build.toplevel path +my $defaultConfig = $ARGV[1] or die; + +# Grub config XML generated by grubConfig function in grub.nix +my $dom = XML::LibXML->load_xml(location => $ARGV[0]); + +sub get { my ($name) = @_; return $dom->findvalue("/expr/attrs/attr[\@name = '$name']/*/\@value"); } + +sub getList { + my ($name) = @_; + my @list = (); + foreach my $entry ($dom->findnodes("/expr/attrs/attr[\@name = '$name']/list/string/\@value")) { + $entry = $entry->findvalue(".") or die; + push(@list, $entry); + } + return @list; +} + +sub readFile { + my ($fn) = @_; + # enable slurp mode: read entire file in one go + local $/ = undef; + open my $fh, "<$fn" or return undef; + my $s = <$fh>; + close $fh; + # disable slurp mode + local $/ = "\n"; + chomp $s; + return $s; +} + +sub writeFile { + my ($fn, $s) = @_; + open my $fh, ">$fn" or die "cannot create $fn: $!\n"; + print $fh $s or die "cannot write to $fn: $!\n"; + close $fh or die "cannot close $fn: $!\n"; +} + +sub runCommand { + open(my $fh, "-|", @_) or die "Failed to execute: $@_\n"; + my @ret = $fh->getlines(); + close $fh; + return ($?, @ret); +} + +my $grub = get("grub"); +my $grubTarget = get("grubTarget"); +my $extraConfig = get("extraConfig"); +my $extraPrepareConfig = get("extraPrepareConfig"); +my $extraPerEntryConfig = get("extraPerEntryConfig"); +my $extraEntries = get("extraEntries"); +my $extraEntriesBeforeNixOS = get("extraEntriesBeforeNixOS") eq "true"; +my $splashImage = get("splashImage"); +my $splashMode = get("splashMode"); +my $entryOptions = get("entryOptions"); +my $subEntryOptions = get("subEntryOptions"); +my $backgroundColor = get("backgroundColor"); +my $configurationLimit = int(get("configurationLimit")); +my $copyKernels = get("copyKernels") eq "true"; +my $timeout = int(get("timeout")); +my $timeoutStyle = get("timeoutStyle"); +my $defaultEntry = get("default"); +my $fsIdentifier = get("fsIdentifier"); +my $grubEfi = get("grubEfi"); +my $grubTargetEfi = get("grubTargetEfi"); +my $bootPath = get("bootPath"); +my $storePath = get("storePath"); +my $canTouchEfiVariables = get("canTouchEfiVariables"); +my $efiInstallAsRemovable = get("efiInstallAsRemovable"); +my $efiSysMountPoint = get("efiSysMountPoint"); +my $gfxmodeEfi = get("gfxmodeEfi"); +my $gfxmodeBios = get("gfxmodeBios"); +my $gfxpayloadEfi = get("gfxpayloadEfi"); +my $gfxpayloadBios = get("gfxpayloadBios"); +my $bootloaderId = get("bootloaderId"); +my $forceInstall = get("forceInstall"); +my $font = get("font"); +my $theme = get("theme"); +my $saveDefault = $defaultEntry eq "saved"; +$ENV{'PATH'} = get("path"); + +print STDERR "updating GRUB 2 menu...\n"; + +mkpath("$bootPath/grub", 0, 0700); + +# Discover whether the bootPath is on the same filesystem as / and +# /nix/store. If not, then all kernels and initrds must be copied to +# the bootPath. +if (stat($bootPath)->dev != stat("/nix/store")->dev) { + $copyKernels = 1; +} + +# Discover information about the location of the bootPath +struct(Fs => { + device => '$', + type => '$', + mount => '$', +}); +sub PathInMount { + my ($path, $mount) = @_; + my @splitMount = split /\//, $mount; + my @splitPath = split /\//, $path; + if ($#splitPath < $#splitMount) { + return 0; + } + for (my $i = 0; $i <= $#splitMount; $i++) { + if ($splitMount[$i] ne $splitPath[$i]) { + return 0; + } + } + return 1; +} + +# Figure out what filesystem is used for the directory with init/initrd/kernel files +sub GetFs { + my ($dir) = @_; + my $bestFs = Fs->new(device => "", type => "", mount => ""); + foreach my $fs (read_file("/proc/self/mountinfo")) { + chomp $fs; + my @fields = split / /, $fs; + my $mountPoint = $fields[4]; + my @mountOptions = split /,/, $fields[5]; + + # Skip the optional fields. + my $n = 6; $n++ while $fields[$n] ne "-"; $n++; + my $fsType = $fields[$n]; + my $device = $fields[$n + 1]; + my @superOptions = split /,/, $fields[$n + 2]; + + # Skip the bind-mount on /nix/store. + next if $mountPoint eq "/nix/store" && (grep { $_ eq "rw" } @superOptions); + # Skip mount point generated by systemd-efi-boot-generator? + next if $fsType eq "autofs"; + + # Ensure this matches the intended directory + next unless PathInMount($dir, $mountPoint); + + # Is it better than our current match? + if (length($mountPoint) > length($bestFs->mount)) { + + # -d performs a stat, which can hang forever on network file systems, + # so we only make this call last, when it's likely that this is the mount point we need. + next unless -d $mountPoint; + + $bestFs = Fs->new(device => $device, type => $fsType, mount => $mountPoint); + } + } + return $bestFs; +} +struct (Grub => { + path => '$', + search => '$', +}); +my $driveid = 1; +sub GrubFs { + my ($dir) = @_; + my $fs = GetFs($dir); + my $path = substr($dir, length($fs->mount)); + if (substr($path, 0, 1) ne "/") { + $path = "/$path"; + } + my $search = ""; + + # ZFS is completely separate logic as zpools are always identified by a label + # or custom UUID + if ($fs->type eq 'zfs') { + my $sid = index($fs->device, '/'); + + if ($sid < 0) { + $search = '--label ' . $fs->device; + $path = '/@' . $path; + } else { + $search = '--label ' . substr($fs->device, 0, $sid); + $path = '/' . substr($fs->device, $sid) . '/@' . $path; + } + } else { + my %types = ('uuid' => '--fs-uuid', 'label' => '--label'); + + if ($fsIdentifier eq 'provided') { + # If the provided dev is identifying the partition using a label or uuid, + # we should get the label / uuid and do a proper search + my @matches = $fs->device =~ m/\/dev\/disk\/by-(label|uuid)\/(.*)/; + if ($#matches > 1) { + die "Too many matched devices" + } elsif ($#matches == 1) { + $search = "$types{$matches[0]} $matches[1]" + } + } else { + # Determine the identifying type + $search = $types{$fsIdentifier} . ' '; + + # Based on the type pull in the identifier from the system + my ($status, @devInfo) = runCommand("@utillinux@/bin/blkid", "-o", "export", @{[$fs->device]}); + if ($status != 0) { + die "Failed to get blkid info (returned $status) for @{[$fs->mount]} on @{[$fs->device]}"; + } + my @matches = join("", @devInfo) =~ m/@{[uc $fsIdentifier]}=([^\n]*)/; + if ($#matches != 0) { + die "Couldn't find a $types{$fsIdentifier} for @{[$fs->device]}\n" + } + $search .= $matches[0]; + } + + # BTRFS is a special case in that we need to fix the referenced path based on subvolumes + if ($fs->type eq 'btrfs') { + my ($status, @id_info) = runCommand("@btrfsprogs@/bin/btrfs", "subvol", "show", @{[$fs->mount]}); + if ($status != 0) { + die "Failed to retrieve subvolume info for @{[$fs->mount]}\n"; + } + my @ids = join("\n", @id_info) =~ m/^(?!\/\n).*Subvolume ID:[ \t\n]*([0-9]+)/s; + if ($#ids > 0) { + die "Btrfs subvol name for @{[$fs->device]} listed multiple times in mount\n" + } elsif ($#ids == 0) { + my ($status, @path_info) = runCommand("@btrfsprogs@/bin/btrfs", "subvol", "list", @{[$fs->mount]}); + if ($status != 0) { + die "Failed to find @{[$fs->mount]} subvolume id from btrfs\n"; + } + my @paths = join("", @path_info) =~ m/ID $ids[0] [^\n]* path ([^\n]*)/; + if ($#paths > 0) { + die "Btrfs returned multiple paths for a single subvolume id, mountpoint @{[$fs->mount]}\n"; + } elsif ($#paths != 0) { + die "Btrfs did not return a path for the subvolume at @{[$fs->mount]}\n"; + } + $path = "/$paths[0]$path"; + } + } + } + if (not $search eq "") { + $search = "search --set=drive$driveid " . $search; + $path = "(\$drive$driveid)$path"; + $driveid += 1; + } + return Grub->new(path => $path, search => $search); +} +my $grubBoot = GrubFs($bootPath); +my $grubStore; +if ($copyKernels == 0) { + $grubStore = GrubFs($storePath); +} + +# Generate the header. +my $conf .= "# Automatically generated. DO NOT EDIT THIS FILE!\n"; + +my @users = (); +foreach my $user ($dom->findnodes('/expr/attrs/attr[@name = "users"]/attrs/attr')) { + my $name = $user->findvalue('@name') or die; + my $hashedPassword = $user->findvalue('./attrs/attr[@name = "hashedPassword"]/string/@value'); + my $hashedPasswordFile = $user->findvalue('./attrs/attr[@name = "hashedPasswordFile"]/string/@value'); + my $password = $user->findvalue('./attrs/attr[@name = "password"]/string/@value'); + my $passwordFile = $user->findvalue('./attrs/attr[@name = "passwordFile"]/string/@value'); + + if ($hashedPasswordFile) { + open(my $f, '<', $hashedPasswordFile) or die "Can't read file '$hashedPasswordFile'!"; + $hashedPassword = <$f>; + chomp $hashedPassword; + } + if ($passwordFile) { + open(my $f, '<', $passwordFile) or die "Can't read file '$passwordFile'!"; + $password = <$f>; + chomp $password; + } + + if ($hashedPassword) { + if (index($hashedPassword, "grub.pbkdf2.") == 0) { + $conf .= "\npassword_pbkdf2 $name $hashedPassword"; + } + else { + die "Password hash for GRUB user '$name' is not valid!"; + } + } + elsif ($password) { + $conf .= "\npassword $name $password"; + } + else { + die "GRUB user '$name' has no password!"; + } + push(@users, $name); +} +if (@users) { + $conf .= "\nset superusers=\"" . join(' ',@users) . "\"\n"; +} + +if ($copyKernels == 0) { + $conf .= " + " . $grubStore->search; +} +# FIXME: should use grub-mkconfig. +my $defaultEntryText = $defaultEntry; +if ($saveDefault) { + $defaultEntryText = "\"\${saved_entry}\""; +} +$conf .= " + " . $grubBoot->search . " + if [ -s \$prefix/grubenv ]; then + load_env + fi + + # ‘grub-reboot’ sets a one-time saved entry, which we process here and + # then delete. + if [ \"\${next_entry}\" ]; then + set default=\"\${next_entry}\" + set next_entry= + save_env next_entry + set timeout=1 + set boot_once=true + else + set default=$defaultEntryText + set timeout=$timeout + fi + set timeout_style=$timeoutStyle + + function savedefault { + if [ -z \"\${boot_once}\"]; then + saved_entry=\"\${chosen}\" + save_env saved_entry + fi + } + + # Setup the graphics stack for bios and efi systems + if [ \"\${grub_platform}\" = \"efi\" ]; then + insmod efi_gop + insmod efi_uga + else + insmod vbe + fi +"; + +if ($font) { + copy $font, "$bootPath/converted-font.pf2" or die "cannot copy $font to $bootPath: $!\n"; + $conf .= " + insmod font + if loadfont " . ($grubBoot->path eq "/" ? "" : $grubBoot->path) . "/converted-font.pf2; then + insmod gfxterm + if [ \"\${grub_platform}\" = \"efi\" ]; then + set gfxmode=$gfxmodeEfi + set gfxpayload=$gfxpayloadEfi + else + set gfxmode=$gfxmodeBios + set gfxpayload=$gfxpayloadBios + fi + terminal_output gfxterm + fi + "; +} +if ($splashImage) { + # Keeps the image's extension. + my ($filename, $dirs, $suffix) = fileparse($splashImage, qr"\..[^.]*$"); + # The module for jpg is jpeg. + if ($suffix eq ".jpg") { + $suffix = ".jpeg"; + } + if ($backgroundColor) { + $conf .= " + background_color '$backgroundColor' + "; + } + copy $splashImage, "$bootPath/background$suffix" or die "cannot copy $splashImage to $bootPath: $!\n"; + $conf .= " + insmod " . substr($suffix, 1) . " + if background_image --mode '$splashMode' " . ($grubBoot->path eq "/" ? "" : $grubBoot->path) . "/background$suffix; then + set color_normal=white/black + set color_highlight=black/white + else + set menu_color_normal=cyan/blue + set menu_color_highlight=white/blue + fi + "; +} + +rmtree("$bootPath/theme") or die "cannot clean up theme folder in $bootPath\n" if -e "$bootPath/theme"; + +if ($theme) { + # Copy theme + rcopy($theme, "$bootPath/theme") or die "cannot copy $theme to $bootPath\n"; + + # Detect which modules will need to be loaded + my $with_png = 0; + my $with_jpeg = 0; + + find({ wanted => sub { + if ($_ =~ /\.png$/i) { + $with_png = 1; + } + elsif ($_ =~ /\.jpe?g$/i) { + $with_jpeg = 1; + } + }, no_chdir => 1 }, $theme); + + if ($with_png) { + $conf .= " + insmod png + " + } + if ($with_jpeg) { + $conf .= " + insmod jpeg + " + } + + $conf .= " + # Sets theme. + set theme=" . ($grubBoot->path eq "/" ? "" : $grubBoot->path) . "/theme/theme.txt + export theme + # Load theme fonts, if any + "; + + find( { wanted => sub { + if ($_ =~ /\.pf2$/i) { + $font = File::Spec->abs2rel($File::Find::name, $theme); + $conf .= " + loadfont " . ($grubBoot->path eq "/" ? "" : $grubBoot->path) . "/theme/$font + "; + } + }, no_chdir => 1 }, $theme ); +} + +$conf .= "$extraConfig\n"; + + +# Generate the menu entries. +$conf .= "\n"; + +my %copied; +mkpath("$bootPath/kernels", 0, 0755) if $copyKernels; + +sub copyToKernelsDir { + my ($path) = @_; + return $grubStore->path . substr($path, length("/nix/store")) unless $copyKernels; + $path =~ /\/nix\/store\/(.*)/ or die; + my $name = $1; $name =~ s/\//-/g; + my $dst = "$bootPath/kernels/$name"; + # Don't copy the file if $dst already exists. This means that we + # have to create $dst atomically to prevent partially copied + # kernels or initrd if this script is ever interrupted. + if (! -e $dst) { + my $tmp = "$dst.tmp"; + copy $path, $tmp or die "cannot copy $path to $tmp: $!\n"; + rename $tmp, $dst or die "cannot rename $tmp to $dst: $!\n"; + } + $copied{$dst} = 1; + return ($grubBoot->path eq "/" ? "" : $grubBoot->path) . "/kernels/$name"; +} + +sub addEntry { + my ($name, $path, $options, $current) = @_; + return unless -e "$path/kernel" && -e "$path/initrd"; + + my $kernel = copyToKernelsDir(Cwd::abs_path("$path/kernel")); + my $initrd = copyToKernelsDir(Cwd::abs_path("$path/initrd")); + + # Include second initrd with secrets + if (-e -x "$path/append-initrd-secrets") { + # Name the initrd secrets after the system from which they're derived. + my $systemName = basename(Cwd::abs_path("$path")); + my $initrdSecretsPath = "$bootPath/kernels/$systemName-secrets"; + + mkpath(dirname($initrdSecretsPath), 0, 0755); + my $oldUmask = umask; + # Make sure initrd is not world readable (won't work if /boot is FAT) + umask 0137; + my $initrdSecretsPathTemp = File::Temp::mktemp("$initrdSecretsPath.XXXXXXXX"); + if (system("$path/append-initrd-secrets", $initrdSecretsPathTemp) != 0) { + if ($current) { + die "failed to create initrd secrets $!\n"; + } else { + say STDERR "warning: failed to create initrd secrets for \"$name\", an older generation"; + say STDERR "note: this is normal after having removed or renamed a file in `boot.initrd.secrets`"; + } + } + # Check whether any secrets were actually added + if (-e $initrdSecretsPathTemp && ! -z _) { + rename $initrdSecretsPathTemp, $initrdSecretsPath or die "failed to move initrd secrets into place: $!\n"; + $copied{$initrdSecretsPath} = 1; + $initrd .= " " . ($grubBoot->path eq "/" ? "" : $grubBoot->path) . "/kernels/$systemName-secrets"; + } else { + unlink $initrdSecretsPathTemp; + rmdir dirname($initrdSecretsPathTemp); + } + umask $oldUmask; + } + + my $xen = -e "$path/xen.gz" ? copyToKernelsDir(Cwd::abs_path("$path/xen.gz")) : undef; + + # FIXME: $confName + + my $kernelParams = + "init=" . Cwd::abs_path("$path/init") . " " . + readFile("$path/kernel-params"); + my $xenParams = $xen && -e "$path/xen-params" ? readFile("$path/xen-params") : ""; + + $conf .= "menuentry \"$name\" " . $options . " {\n"; + if ($saveDefault) { + $conf .= " savedefault\n"; + } + $conf .= $grubBoot->search . "\n"; + if ($copyKernels == 0) { + $conf .= $grubStore->search . "\n"; + } + $conf .= " $extraPerEntryConfig\n" if $extraPerEntryConfig; + $conf .= " multiboot $xen $xenParams\n" if $xen; + $conf .= " " . ($xen ? "module" : "linux") . " $kernel $kernelParams\n"; + $conf .= " " . ($xen ? "module" : "initrd") . " $initrd\n"; + $conf .= "}\n\n"; +} + +sub addGeneration { + my ($name, $nameSuffix, $path, $options, $current) = @_; + + # Do not search for grand children + my @links = sort (glob "$path/specialisation/*"); + + if ($current != 1 && scalar(@links) != 0) { + $conf .= "submenu \"> $name$nameSuffix\" --class submenu {\n"; + } + + addEntry("$name" . (scalar(@links) == 0 ? "" : " - Default") . $nameSuffix, $path, $options, $current); + + # Find all the children of the current default configuration + # Do not search for grand children + foreach my $link (@links) { + + my $entryName = ""; + + my $cfgName = readFile("$link/configuration-name"); + + my $date = strftime("%F", localtime(lstat($link)->mtime)); + my $version = + -e "$link/nixos-version" + ? readFile("$link/nixos-version") + : basename((glob(dirname(Cwd::abs_path("$link/kernel")) . "/lib/modules/*"))[0]); + + if ($cfgName) { + $entryName = $cfgName; + } else { + my $linkname = basename($link); + $entryName = "($linkname - $date - $version)"; + } + addEntry("$name - $entryName", $link, "", 1); + } + + if ($current != 1 && scalar(@links) != 0) { + $conf .= "}\n"; + } +} + +# Add default entries. +$conf .= "$extraEntries\n" if $extraEntriesBeforeNixOS; + +addGeneration("@distroName@", "", $defaultConfig, $entryOptions, 1); + +$conf .= "$extraEntries\n" unless $extraEntriesBeforeNixOS; + +my $grubBootPath = $grubBoot->path; +# extraEntries could refer to @bootRoot@, which we have to substitute +$conf =~ s/\@bootRoot\@/$grubBootPath/g; + +# Emit submenus for all system profiles. +sub addProfile { + my ($profile, $description) = @_; + + # Add entries for all generations of this profile. + $conf .= "submenu \"$description\" --class submenu {\n"; + + sub nrFromGen { my ($x) = @_; $x =~ /\/\w+-(\d+)-link/; return $1; } + + my @links = sort + { nrFromGen($b) <=> nrFromGen($a) } + (glob "$profile-*-link"); + + my $curEntry = 0; + foreach my $link (@links) { + last if $curEntry++ >= $configurationLimit; + if (! -e "$link/nixos-version") { + warn "skipping corrupt system profile entry ‘$link’\n"; + next; + } + my $date = strftime("%F", localtime(lstat($link)->mtime)); + my $version = + -e "$link/nixos-version" + ? readFile("$link/nixos-version") + : basename((glob(dirname(Cwd::abs_path("$link/kernel")) . "/lib/modules/*"))[0]); + addGeneration("@distroName@ - Configuration " . nrFromGen($link), " ($date - $version)", $link, $subEntryOptions, 0); + } + + $conf .= "}\n"; +} + +addProfile "/nix/var/nix/profiles/system", "@distroName@ - All configurations"; + +for my $profile (glob "/nix/var/nix/profiles/system-profiles/*") { + my $name = basename($profile); + next unless $name =~ /^\w+$/; + addProfile $profile, "@distroName@ - Profile '$name'"; +} + +# 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)); +} + +# write the GRUB config. +my $confFile = "$bootPath/grub/grub.cfg"; +my $tmpFile = $confFile . ".tmp"; +writeFile($tmpFile, $conf); + + +# check whether to install GRUB EFI or not +sub getEfiTarget { + if (($grub ne "") && ($grubEfi ne "")) { + # EFI can only be installed when target is set; + # A target is also required then for non-EFI grub + if (($grubTarget eq "") || ($grubTargetEfi eq "")) { die } + else { return "both" } + } elsif (($grub ne "") && ($grubEfi eq "")) { + # TODO: It would be safer to disallow non-EFI grub installation if no target is given. + # If no target is given, then grub auto-detects the target which can lead to errors. + # E.g. it seems as if grub would auto-detect a EFI target based on the availability + # of a EFI partition. + # However, it seems as auto-detection is currently relied on for non-x86_64 and non-i386 + # architectures in NixOS. That would have to be fixed in the nixos modules first. + return "no" + } elsif (($grub eq "") && ($grubEfi ne "")) { + # EFI can only be installed when target is set; + if ($grubTargetEfi eq "") { die } + else {return "only" } + } else { + # prevent an installation if neither grub nor grubEfi is given + return "neither" + } +} + +my $efiTarget = getEfiTarget(); + +# Append entries detected by os-prober +if (get("useOSProber") eq "true") { + if ($saveDefault) { + # os-prober will read this to determine if "savedefault" should be added to generated entries + $ENV{'GRUB_SAVEDEFAULT'} = "true"; + } + + my $targetpackage = ($efiTarget eq "no") ? $grub : $grubEfi; + system(get("shell"), "-c", "pkgdatadir=$targetpackage/share/grub $targetpackage/etc/grub.d/30_os-prober >> $tmpFile"); +} + +# Atomically switch to the new config +rename $tmpFile, $confFile or die "cannot rename $tmpFile to $confFile: $!\n"; + + +# Remove obsolete files from $bootPath/kernels. +foreach my $fn (glob "$bootPath/kernels/*") { + next if defined $copied{$fn}; + print STDERR "removing obsolete file $fn\n"; + unlink $fn; +} + + +# +# Install GRUB if the parameters changed from the last time we installed it. +# + +struct(GrubState => { + name => '$', + version => '$', + efi => '$', + devices => '$', + efiMountPoint => '$', + extraGrubInstallArgs => '@', +}); +# If you add something to the state file, only add it to the end +# because it is read line-by-line. +sub readGrubState { + my $defaultGrubState = GrubState->new(name => "", version => "", efi => "", devices => "", efiMountPoint => "", extraGrubInstallArgs => () ); + open FILE, "<$bootPath/grub/state" or return $defaultGrubState; + local $/ = "\n"; + my $name = <FILE>; + chomp($name); + my $version = <FILE>; + chomp($version); + my $efi = <FILE>; + chomp($efi); + my $devices = <FILE>; + chomp($devices); + my $efiMountPoint = <FILE>; + chomp($efiMountPoint); + # Historically, arguments in the state file were one per each line, but that + # gets really messy when newlines are involved, structured arguments + # like lists are needed (they have to have a separator encoding), or even worse, + # when we need to remove a setting in the future. Thus, the 6th line is a JSON + # object that can store structured data, with named keys, and all new state + # should go in there. + my $jsonStateLine = <FILE>; + # For historical reasons we do not check the values above for un-definedness + # (that is, when the state file has too few lines and EOF is reached), + # because the above come from the first version of this logic and are thus + # guaranteed to be present. + $jsonStateLine = defined $jsonStateLine ? $jsonStateLine : '{}'; # empty JSON object + chomp($jsonStateLine); + if ($jsonStateLine eq "") { + $jsonStateLine = '{}'; # empty JSON object + } + my %jsonState = %{decode_json($jsonStateLine)}; + my @extraGrubInstallArgs = exists($jsonState{'extraGrubInstallArgs'}) ? @{$jsonState{'extraGrubInstallArgs'}} : (); + close FILE; + my $grubState = GrubState->new(name => $name, version => $version, efi => $efi, devices => $devices, efiMountPoint => $efiMountPoint, extraGrubInstallArgs => \@extraGrubInstallArgs ); + return $grubState +} + +my @deviceTargets = getList('devices'); +my $prevGrubState = readGrubState(); +my @prevDeviceTargets = split/,/, $prevGrubState->devices; +my @extraGrubInstallArgs = getList('extraGrubInstallArgs'); +my @prevExtraGrubInstallArgs = @{$prevGrubState->extraGrubInstallArgs}; + +my $devicesDiffer = scalar (List::Compare->new( '-u', '-a', \@deviceTargets, \@prevDeviceTargets)->get_symmetric_difference()); +my $extraGrubInstallArgsDiffer = scalar (List::Compare->new( '-u', '-a', \@extraGrubInstallArgs, \@prevExtraGrubInstallArgs)->get_symmetric_difference()); +my $nameDiffer = get("fullName") ne $prevGrubState->name; +my $versionDiffer = get("fullVersion") ne $prevGrubState->version; +my $efiDiffer = $efiTarget ne $prevGrubState->efi; +my $efiMountPointDiffer = $efiSysMountPoint ne $prevGrubState->efiMountPoint; +if (($ENV{'NIXOS_INSTALL_GRUB'} // "") eq "1") { + warn "NIXOS_INSTALL_GRUB env var deprecated, use NIXOS_INSTALL_BOOTLOADER"; + $ENV{'NIXOS_INSTALL_BOOTLOADER'} = "1"; +} +my $requireNewInstall = $devicesDiffer || $extraGrubInstallArgsDiffer || $nameDiffer || $versionDiffer || $efiDiffer || $efiMountPointDiffer || (($ENV{'NIXOS_INSTALL_BOOTLOADER'} // "") eq "1"); + +# install a symlink so that grub can detect the boot drive +my $tmpDir = File::Temp::tempdir(CLEANUP => 1) or die "Failed to create temporary space: $!"; +symlink "$bootPath", "$tmpDir/boot" or die "Failed to symlink $tmpDir/boot: $!"; + +# install non-EFI GRUB +if (($requireNewInstall != 0) && ($efiTarget eq "no" || $efiTarget eq "both")) { + foreach my $dev (@deviceTargets) { + next if $dev eq "nodev"; + print STDERR "installing the GRUB 2 boot loader on $dev...\n"; + my @command = ("$grub/sbin/grub-install", "--recheck", "--root-directory=$tmpDir", Cwd::abs_path($dev), @extraGrubInstallArgs); + if ($forceInstall eq "true") { + push @command, "--force"; + } + if ($grubTarget ne "") { + push @command, "--target=$grubTarget"; + } + (system @command) == 0 or die "$0: installation of GRUB on $dev failed: $!\n"; + } +} + + +# install EFI GRUB +if (($requireNewInstall != 0) && ($efiTarget eq "only" || $efiTarget eq "both")) { + print STDERR "installing the GRUB 2 boot loader into $efiSysMountPoint...\n"; + my @command = ("$grubEfi/sbin/grub-install", "--recheck", "--target=$grubTargetEfi", "--boot-directory=$bootPath", "--efi-directory=$efiSysMountPoint", @extraGrubInstallArgs); + if ($forceInstall eq "true") { + push @command, "--force"; + } + push @command, "--bootloader-id=$bootloaderId"; + if ($canTouchEfiVariables ne "true") { + push @command, "--no-nvram"; + push @command, "--removable" if $efiInstallAsRemovable eq "true"; + } + + (system @command) == 0 or die "$0: installation of GRUB EFI into $efiSysMountPoint failed: $!\n"; +} + + +# update GRUB state file +if ($requireNewInstall != 0) { + # Temp file for atomic rename. + my $stateFile = "$bootPath/grub/state"; + my $stateFileTmp = $stateFile . ".tmp"; + + open FILE, ">$stateFileTmp" or die "cannot create $stateFileTmp: $!\n"; + print FILE get("fullName"), "\n" or die; + print FILE get("fullVersion"), "\n" or die; + print FILE $efiTarget, "\n" or die; + print FILE join( ",", @deviceTargets ), "\n" or die; + print FILE $efiSysMountPoint, "\n" or die; + my %jsonState = ( + extraGrubInstallArgs => \@extraGrubInstallArgs + ); + my $jsonStateLine = encode_json(\%jsonState); + print FILE $jsonStateLine, "\n" or die; + close FILE or die; + + # Atomically switch to the new state file + rename $stateFileTmp, $stateFile or die "cannot rename $stateFileTmp to $stateFile: $!\n"; +} diff --git a/nixpkgs/nixos/modules/system/boot/loader/grub/ipxe.nix b/nixpkgs/nixos/modules/system/boot/loader/grub/ipxe.nix new file mode 100644 index 000000000000..d926b7ceaa6e --- /dev/null +++ b/nixpkgs/nixos/modules/system/boot/loader/grub/ipxe.nix @@ -0,0 +1,60 @@ +# This module adds a scripted iPXE entry to the GRUB boot menu. + +{ config, lib, pkgs, ... }: + +with lib; + +let + scripts = builtins.attrNames config.boot.loader.grub.ipxe; + + grubEntry = name: + '' + menuentry "iPXE - ${name}" { + linux16 @bootRoot@/ipxe.lkrn + initrd16 @bootRoot@/${name}.ipxe + } + + ''; + + scriptFile = name: + let + value = builtins.getAttr name config.boot.loader.grub.ipxe; + in + if builtins.typeOf value == "path" then value + else builtins.toFile "${name}.ipxe" value; +in +{ + options = + { boot.loader.grub.ipxe = mkOption { + type = types.attrsOf (types.either types.path types.str); + description = + lib.mdDoc '' + Set of iPXE scripts available for + booting from the GRUB boot menu. + ''; + default = { }; + example = literalExpression '' + { demo = ''' + #!ipxe + dhcp + chain http://boot.ipxe.org/demo/boot.php + '''; + } + ''; + }; + }; + + config = mkIf (builtins.length scripts != 0) { + + boot.loader.grub.extraEntries = toString (map grubEntry scripts); + + boot.loader.grub.extraFiles = + { "ipxe.lkrn" = "${pkgs.ipxe}/ipxe.lkrn"; } + // + builtins.listToAttrs ( map + (name: { name = name+".ipxe"; value = scriptFile name; }) + scripts + ); + }; + +} diff --git a/nixpkgs/nixos/modules/system/boot/loader/grub/memtest.nix b/nixpkgs/nixos/modules/system/boot/loader/grub/memtest.nix new file mode 100644 index 000000000000..8e68431ac571 --- /dev/null +++ b/nixpkgs/nixos/modules/system/boot/loader/grub/memtest.nix @@ -0,0 +1,69 @@ +# This module adds Memtest86+ to the GRUB boot menu. +{ config, lib, pkgs, ... }: + +with lib; + +let + memtest86 = pkgs.memtest86plus; + cfg = config.boot.loader.grub.memtest86; +in + +{ + options = { + + boot.loader.grub.memtest86 = { + + enable = mkOption { + default = false; + type = types.bool; + description = lib.mdDoc '' + Make Memtest86+, a memory testing program, available from the GRUB + boot menu. + ''; + }; + + params = mkOption { + default = []; + example = [ "console=ttyS0,115200" ]; + type = types.listOf types.str; + description = lib.mdDoc '' + Parameters added to the Memtest86+ command line. As of memtest86+ 5.01 + the following list of (apparently undocumented) parameters are + accepted: + + - `console=...`, set up a serial console. + Examples: + `console=ttyS0`, + `console=ttyS0,9600` or + `console=ttyS0,115200n8`. + + - `btrace`, enable boot trace. + + - `maxcpus=N`, limit number of CPUs. + + - `onepass`, run one pass and exit if there + are no errors. + + - `tstlist=...`, list of tests to run. + Example: `0,1,2`. + + - `cpumask=...`, set a CPU mask, to select CPUs + to use for testing. + + This list of command line options was obtained by reading the + Memtest86+ source code. + ''; + }; + + }; + }; + + config = mkIf cfg.enable { + boot.loader.grub.extraEntries = '' + menuentry "Memtest86+" { + linux @bootRoot@/memtest.bin ${toString cfg.params} + } + ''; + boot.loader.grub.extraFiles."memtest.bin" = "${memtest86}/memtest.bin"; + }; +} diff --git a/nixpkgs/nixos/modules/system/boot/loader/init-script/init-script-builder.sh b/nixpkgs/nixos/modules/system/boot/loader/init-script/init-script-builder.sh new file mode 100644 index 000000000000..755ea259c425 --- /dev/null +++ b/nixpkgs/nixos/modules/system/boot/loader/init-script/init-script-builder.sh @@ -0,0 +1,92 @@ +#! @bash@/bin/sh -e + +shopt -s nullglob + +export PATH=/empty +for i in @path@; do PATH=$PATH:$i/bin; done + +if test $# -ne 1; then + echo "Usage: init-script-builder.sh DEFAULT-CONFIG" + exit 1 +fi + +defaultConfig="$1" + + +[ "$(stat -f -c '%i' /)" = "$(stat -f -c '%i' /boot)" ] || { + # see grub-menu-builder.sh + echo "WARNING: /boot being on a different filesystem not supported by init-script-builder.sh" +} + + + +target="/sbin/init" +targetOther="/boot/init-other-configurations-contents.txt" + +tmp="$target.tmp" +tmpOther="$targetOther.tmp" + + +configurationCounter=0 +numAlienEntries=`cat <<EOF | egrep '^[[:space:]]*title' | wc -l +@extraEntries@ +EOF` + + + + +# Add an entry to $targetOther +addEntry() { + local name="$1" + local path="$2" + local shortSuffix="$3" + + configurationCounter=$((configurationCounter + 1)) + + local stage2=$path/init + + content="$( + echo "#!/bin/sh" + echo "# $name" + echo "# created by init-script-builder.sh" + echo "exec $stage2" + )" + + [ "$path" != "$defaultConfig" ] || { + echo "$content" > $tmp + echo "# older configurations: $targetOther" >> $tmp + chmod +x $tmp + } + + echo -e "$content\n\n" >> $tmpOther +} + + +mkdir -p /boot /sbin + +addEntry "@distroName@ - 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/specialisation/* ) | sort -n); do + date=$(stat --printf="%y\n" $link | sed 's/\..*//') + addEntry "@distroName@ - variation" $link "" +done + +for generation in $( + (cd /nix/var/nix/profiles && ls -d system-*-link) \ + | sed 's/system-\([0-9]\+\)-link/\1/' \ + | sort -n -r); do + link=/nix/var/nix/profiles/system-$generation-link + date=$(stat --printf="%y\n" $link | sed 's/\..*//') + if [ -d $link/kernel ]; then + kernelVersion=$(cd $(dirname $(readlink -f $link/kernel))/lib/modules && echo *) + suffix="($date - $kernelVersion)" + else + suffix="($date)" + fi + addEntry "@distroName@ - Configuration $generation $suffix" $link "$generation ($date)" +done + +mv $tmpOther $targetOther +mv $tmp $target diff --git a/nixpkgs/nixos/modules/system/boot/loader/init-script/init-script.nix b/nixpkgs/nixos/modules/system/boot/loader/init-script/init-script.nix new file mode 100644 index 000000000000..4d33ed6b665b --- /dev/null +++ b/nixpkgs/nixos/modules/system/boot/loader/init-script/init-script.nix @@ -0,0 +1,52 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + + initScriptBuilder = pkgs.substituteAll { + src = ./init-script-builder.sh; + isExecutable = true; + inherit (pkgs) bash; + inherit (config.system.nixos) distroName; + path = [pkgs.coreutils pkgs.gnused pkgs.gnugrep]; + }; + +in + +{ + + ###### interface + + options = { + + boot.loader.initScript = { + + enable = mkOption { + default = false; + type = types.bool; + description = lib.mdDoc '' + Some systems require a /sbin/init script which is started. + Or having it makes starting NixOS easier. + This applies to some kind of hosting services and user mode linux. + + Additionally this script will create + /boot/init-other-configurations-contents.txt containing + contents of remaining configurations. You can copy paste them into + /sbin/init manually running a rescue system or such. + ''; + }; + }; + + }; + + + ###### implementation + + config = mkIf config.boot.loader.initScript.enable { + + system.build.installBootLoader = initScriptBuilder; + + }; + +} diff --git a/nixpkgs/nixos/modules/system/boot/loader/loader.nix b/nixpkgs/nixos/modules/system/boot/loader/loader.nix new file mode 100644 index 000000000000..0e33264271bf --- /dev/null +++ b/nixpkgs/nixos/modules/system/boot/loader/loader.nix @@ -0,0 +1,20 @@ +{ lib, ... }: + +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; + type = types.nullOr types.int; + description = lib.mdDoc '' + Timeout (in seconds) until loader boots the default menu item. Use null if the loader menu should be displayed indefinitely. + ''; + }; + }; +} diff --git a/nixpkgs/nixos/modules/system/boot/loader/raspberrypi/raspberrypi-builder.nix b/nixpkgs/nixos/modules/system/boot/loader/raspberrypi/raspberrypi-builder.nix new file mode 100644 index 000000000000..64e106036abd --- /dev/null +++ b/nixpkgs/nixos/modules/system/boot/loader/raspberrypi/raspberrypi-builder.nix @@ -0,0 +1,9 @@ +{ pkgs, configTxt, firmware ? pkgs.raspberrypifw }: + +pkgs.substituteAll { + src = ./raspberrypi-builder.sh; + isExecutable = true; + inherit (pkgs) bash; + path = [pkgs.coreutils pkgs.gnused pkgs.gnugrep]; + inherit firmware configTxt; +} diff --git a/nixpkgs/nixos/modules/system/boot/loader/raspberrypi/raspberrypi-builder.sh b/nixpkgs/nixos/modules/system/boot/loader/raspberrypi/raspberrypi-builder.sh new file mode 100644 index 000000000000..0541ca1ba622 --- /dev/null +++ b/nixpkgs/nixos/modules/system/boot/loader/raspberrypi/raspberrypi-builder.sh @@ -0,0 +1,143 @@ +#! @bash@/bin/sh + +# This can end up being called disregarding the shebang. +set -e + +shopt -s nullglob + +export PATH=/empty +for i in @path@; do PATH=$PATH:$i/bin; done + +usage() { + echo "usage: $0 -c <path-to-default-configuration> [-d <boot-dir>]" >&2 + exit 1 +} + +default= # Default configuration +target=/boot # Target directory + +while getopts "c:d:" opt; do + case "$opt" in + c) default="$OPTARG" ;; + d) target="$OPTARG" ;; + \?) usage ;; + esac +done + +echo "updating the boot generations directory..." + +mkdir -p $target/old + +# Convert a path to a file in the Nix store such as +# /nix/store/<hash>-<name>/file to <hash>-<name>-<file>. +cleanName() { + local path="$1" + echo "$path" | sed 's|^/nix/store/||' | sed 's|/|-|g' +} + +# Copy a file from the Nix store to $target/kernels. +declare -A filesCopied + +copyToKernelsDir() { + local src="$1" + local dst="$target/old/$(cleanName $src)" + # Don't copy the file if $dst already exists. This means that we + # have to create $dst atomically to prevent partially copied + # kernels or initrd if this script is ever interrupted. + if ! test -e $dst; then + local dstTmp=$dst.tmp.$$ + cp $src $dstTmp + mv $dstTmp $dst + fi + filesCopied[$dst]=1 + result=$dst +} + +copyForced() { + local src="$1" + local dst="$2" + cp $src $dst.tmp + mv $dst.tmp $dst +} + +outdir=$target/old +mkdir -p $outdir || true + +# Copy its kernel and initrd to $target/old. +addEntry() { + local path="$1" + local generation="$2" + + if ! test -e $path/kernel -a -e $path/initrd; then + return + fi + + local kernel=$(readlink -f $path/kernel) + local initrd=$(readlink -f $path/initrd) + local dtb_path=$(readlink -f $path/dtbs) + + if test -n "@copyKernels@"; then + copyToKernelsDir $kernel; kernel=$result + copyToKernelsDir $initrd; initrd=$result + fi + + echo $(readlink -f $path) > $outdir/$generation-system + echo $(readlink -f $path/init) > $outdir/$generation-init + cp $path/kernel-params $outdir/$generation-cmdline.txt + echo $initrd > $outdir/$generation-initrd + echo $kernel > $outdir/$generation-kernel + + if test "$generation" = "default"; then + copyForced $kernel $target/kernel.img + copyForced $initrd $target/initrd + for dtb in $dtb_path/{broadcom,}/bcm*.dtb; do + dst="$target/$(basename $dtb)" + copyForced $dtb "$dst" + filesCopied[$dst]=1 + done + cp "$(readlink -f "$path/init")" $target/nixos-init + echo "`cat $path/kernel-params` init=$path/init" >$target/cmdline.txt + fi +} + +addEntry $default default + +# Add all generations of the system profile to the menu, in reverse +# (most recent to least recent) order. +for generation in $( + (cd /nix/var/nix/profiles && ls -d system-*-link) \ + | sed 's/system-\([0-9]\+\)-link/\1/' \ + | sort -n -r); do + link=/nix/var/nix/profiles/system-$generation-link + addEntry $link $generation +done + +# Add the firmware files +fwdir=@firmware@/share/raspberrypi/boot/ +copyForced $fwdir/bootcode.bin $target/bootcode.bin +copyForced $fwdir/fixup.dat $target/fixup.dat +copyForced $fwdir/fixup4.dat $target/fixup4.dat +copyForced $fwdir/fixup4cd.dat $target/fixup4cd.dat +copyForced $fwdir/fixup4db.dat $target/fixup4db.dat +copyForced $fwdir/fixup4x.dat $target/fixup4x.dat +copyForced $fwdir/fixup_cd.dat $target/fixup_cd.dat +copyForced $fwdir/fixup_db.dat $target/fixup_db.dat +copyForced $fwdir/fixup_x.dat $target/fixup_x.dat +copyForced $fwdir/start.elf $target/start.elf +copyForced $fwdir/start4.elf $target/start4.elf +copyForced $fwdir/start4cd.elf $target/start4cd.elf +copyForced $fwdir/start4db.elf $target/start4db.elf +copyForced $fwdir/start4x.elf $target/start4x.elf +copyForced $fwdir/start_cd.elf $target/start_cd.elf +copyForced $fwdir/start_db.elf $target/start_db.elf +copyForced $fwdir/start_x.elf $target/start_x.elf + +# Add the config.txt +copyForced @configTxt@ $target/config.txt + +# Remove obsolete files from $target and $target/old. +for fn in $target/old/*linux* $target/old/*initrd-initrd* $target/bcm*.dtb; do + if ! test "${filesCopied[$fn]}" = 1; then + rm -vf -- "$fn" + fi +done diff --git a/nixpkgs/nixos/modules/system/boot/loader/raspberrypi/raspberrypi.nix b/nixpkgs/nixos/modules/system/boot/loader/raspberrypi/raspberrypi.nix new file mode 100644 index 000000000000..9c9bee93de8a --- /dev/null +++ b/nixpkgs/nixos/modules/system/boot/loader/raspberrypi/raspberrypi.nix @@ -0,0 +1,151 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + cfg = config.boot.loader.raspberryPi; + + builderUboot = import ./uboot-builder.nix { inherit pkgs configTxt; inherit (cfg) version; }; + builderGeneric = import ./raspberrypi-builder.nix { inherit pkgs configTxt; }; + + builder = + if cfg.uboot.enable then + "${builderUboot} -g ${toString cfg.uboot.configurationLimit} -t ${timeoutStr} -c" + else + "${builderGeneric} -c"; + + blCfg = config.boot.loader; + timeoutStr = if blCfg.timeout == null then "-1" else toString blCfg.timeout; + + isAarch64 = pkgs.stdenv.hostPlatform.isAarch64; + optional = pkgs.lib.optionalString; + + configTxt = + pkgs.writeText "config.txt" ('' + # U-Boot used to need this to work, regardless of whether UART is actually used or not. + # TODO: check when/if this can be removed. + enable_uart=1 + + # Prevent the firmware from smashing the framebuffer setup done by the mainline kernel + # when attempting to show low-voltage or overtemperature warnings. + avoid_warnings=1 + '' + optional isAarch64 '' + # Boot in 64-bit mode. + arm_64bit=1 + '' + (if cfg.uboot.enable then '' + kernel=u-boot-rpi.bin + '' else '' + kernel=kernel.img + initramfs initrd followkernel + '') + optional (cfg.firmwareConfig != null) cfg.firmwareConfig); + +in + +{ + options = { + + boot.loader.raspberryPi = { + enable = mkOption { + default = false; + type = types.bool; + description = lib.mdDoc '' + Whether to create files with the system generations in + `/boot`. + `/boot/old` will hold files from old generations. + + ::: {.note} + These options are deprecated, unsupported, and may not work like expected. + ::: + ''; + }; + + version = mkOption { + default = 2; + type = types.enum [ 0 1 2 3 4 ]; + description = lib.mdDoc ""; + }; + + uboot = { + enable = mkOption { + default = false; + type = types.bool; + description = lib.mdDoc '' + Enable using uboot as bootmanager for the raspberry pi. + + ::: {.note} + These options are deprecated, unsupported, and may not work like expected. + ::: + ''; + }; + + configurationLimit = mkOption { + default = 20; + example = 10; + type = types.int; + description = lib.mdDoc '' + Maximum number of configurations in the boot menu. + + ::: {.note} + These options are deprecated, unsupported, and may not work like expected. + ::: + ''; + }; + + }; + + firmwareConfig = mkOption { + default = null; + type = types.nullOr types.lines; + description = lib.mdDoc '' + Extra options that will be appended to `/boot/config.txt` file. + For possible values, see: https://www.raspberrypi.com/documentation/computers/config_txt.html + + ::: {.note} + These options are deprecated, unsupported, and may not work like expected. + ::: + ''; + }; + }; + }; + + config = mkMerge[ + (mkIf cfg.uboot.enable { + warnings = [ + '' + The option set for `boot.loader.raspberrypi.uboot` has been recommended against + for years, and is now formally deprecated. + + It is possible it already did not work like you expected. + + It never worked on the Raspberry Pi 4 family. + + These options will be removed by NixOS 24.11. + '' + ]; + }) + (mkIf cfg.enable { + warnings = [ + '' + The option set for `boot.loader.raspberrypi` has been recommended against + for years, and is now formally deprecated. + + It is possible it already did not work like you expected. + + It never worked on the Raspberry Pi 4 family. + + These options will be removed by NixOS 24.11. + '' + ]; + }) + (mkIf cfg.enable { + assertions = singleton { + assertion = !pkgs.stdenv.hostPlatform.isAarch64 || cfg.version >= 3; + message = "Only Raspberry Pi >= 3 supports aarch64."; + }; + + system.build.installBootLoader = builder; + system.boot.loader.id = "raspberrypi"; + system.boot.loader.kernelFile = pkgs.stdenv.hostPlatform.linux-kernel.target; + }) + ]; +} diff --git a/nixpkgs/nixos/modules/system/boot/loader/raspberrypi/uboot-builder.nix b/nixpkgs/nixos/modules/system/boot/loader/raspberrypi/uboot-builder.nix new file mode 100644 index 000000000000..a4352ab9a240 --- /dev/null +++ b/nixpkgs/nixos/modules/system/boot/loader/raspberrypi/uboot-builder.nix @@ -0,0 +1,37 @@ +{ pkgs, version, configTxt }: + +let + isAarch64 = pkgs.stdenv.hostPlatform.isAarch64; + + uboot = + if version == 0 then + pkgs.ubootRaspberryPiZero + else if version == 1 then + pkgs.ubootRaspberryPi + else if version == 2 then + pkgs.ubootRaspberryPi2 + else if version == 3 then + if isAarch64 then + pkgs.ubootRaspberryPi3_64bit + else + pkgs.ubootRaspberryPi3_32bit + else + throw "U-Boot is not yet supported on the raspberry pi 4."; + + extlinuxConfBuilder = + import ../generic-extlinux-compatible/extlinux-conf-builder.nix { + pkgs = pkgs.buildPackages; + }; +in +pkgs.substituteAll { + src = ./uboot-builder.sh; + isExecutable = true; + inherit (pkgs) bash; + path = [pkgs.coreutils pkgs.gnused pkgs.gnugrep]; + firmware = pkgs.raspberrypifw; + inherit uboot; + inherit configTxt; + inherit extlinuxConfBuilder; + inherit version; +} + diff --git a/nixpkgs/nixos/modules/system/boot/loader/raspberrypi/uboot-builder.sh b/nixpkgs/nixos/modules/system/boot/loader/raspberrypi/uboot-builder.sh new file mode 100644 index 000000000000..ea591427179f --- /dev/null +++ b/nixpkgs/nixos/modules/system/boot/loader/raspberrypi/uboot-builder.sh @@ -0,0 +1,38 @@ +#! @bash@/bin/sh -e + +target=/boot # Target directory + +while getopts "t:c:d:g:" opt; do + case "$opt" in + d) target="$OPTARG" ;; + *) ;; + esac +done + +copyForced() { + local src="$1" + local dst="$2" + cp $src $dst.tmp + mv $dst.tmp $dst +} + +# Call the extlinux builder +"@extlinuxConfBuilder@" "$@" + +# Add the firmware files +fwdir=@firmware@/share/raspberrypi/boot/ +copyForced $fwdir/bootcode.bin $target/bootcode.bin +copyForced $fwdir/fixup.dat $target/fixup.dat +copyForced $fwdir/fixup_cd.dat $target/fixup_cd.dat +copyForced $fwdir/fixup_db.dat $target/fixup_db.dat +copyForced $fwdir/fixup_x.dat $target/fixup_x.dat +copyForced $fwdir/start.elf $target/start.elf +copyForced $fwdir/start_cd.elf $target/start_cd.elf +copyForced $fwdir/start_db.elf $target/start_db.elf +copyForced $fwdir/start_x.elf $target/start_x.elf + +# Add the uboot file +copyForced @uboot@/u-boot.bin $target/u-boot-rpi.bin + +# Add the config.txt +copyForced @configTxt@ $target/config.txt diff --git a/nixpkgs/nixos/modules/system/boot/loader/systemd-boot/systemd-boot-builder.py b/nixpkgs/nixos/modules/system/boot/loader/systemd-boot/systemd-boot-builder.py new file mode 100644 index 000000000000..a9978d7adf80 --- /dev/null +++ b/nixpkgs/nixos/modules/system/boot/loader/systemd-boot/systemd-boot-builder.py @@ -0,0 +1,356 @@ +#! @python3@/bin/python3 -B +import argparse +import ctypes +import datetime +import errno +import glob +import os +import os.path +import re +import shutil +import subprocess +import sys +import warnings +import json +from typing import NamedTuple, Dict, List +from dataclasses import dataclass + +# These values will be replaced with actual values during the package build +EFI_SYS_MOUNT_POINT = "@efiSysMountPoint@" +TIMEOUT = "@timeout@" +EDITOR = "@editor@" == "1" +CONSOLE_MODE = "@consoleMode@" +BOOTSPEC_TOOLS = "@bootspecTools@" +DISTRO_NAME = "@distroName@" +NIX = "@nix@" +SYSTEMD = "@systemd@" +CONFIGURATION_LIMIT = int("@configurationLimit@") +CAN_TOUCH_EFI_VARIABLES = "@canTouchEfiVariables@" +GRACEFUL = "@graceful@" +COPY_EXTRA_FILES = "@copyExtraFiles@" + +@dataclass +class BootSpec: + init: str + initrd: str + kernel: str + kernelParams: List[str] + label: str + system: str + toplevel: str + specialisations: Dict[str, "BootSpec"] + initrdSecrets: str | None = None + + +libc = ctypes.CDLL("libc.so.6") + +class SystemIdentifier(NamedTuple): + profile: str | None + generation: int + specialisation: str | None + + +def copy_if_not_exists(source: str, dest: str) -> None: + if not os.path.exists(dest): + shutil.copyfile(source, dest) + + +def generation_dir(profile: str | None, generation: int) -> str: + if profile: + return "/nix/var/nix/profiles/system-profiles/%s-%d-link" % (profile, generation) + else: + return "/nix/var/nix/profiles/system-%d-link" % (generation) + +def system_dir(profile: str | None, generation: int, specialisation: str | None) -> str: + d = generation_dir(profile, generation) + if specialisation: + return os.path.join(d, "specialisation", specialisation) + else: + return d + +BOOT_ENTRY = """title {title} +version Generation {generation} {description} +linux {kernel} +initrd {initrd} +options {kernel_params} +""" + +def generation_conf_filename(profile: str | None, generation: int, specialisation: str | None) -> str: + pieces = [ + "nixos", + profile or None, + "generation", + str(generation), + f"specialisation-{specialisation}" if specialisation else None, + ] + return "-".join(p for p in pieces if p) + ".conf" + + +def write_loader_conf(profile: str | None, generation: int, specialisation: str | None) -> None: + with open(f"{EFI_SYS_MOUNT_POINT}/loader/loader.conf.tmp", 'w') as f: + if TIMEOUT != "": + f.write(f"timeout {TIMEOUT}\n") + f.write("default %s\n" % generation_conf_filename(profile, generation, specialisation)) + if not EDITOR: + f.write("editor 0\n") + f.write(f"console-mode {CONSOLE_MODE}\n") + f.flush() + os.fsync(f.fileno()) + os.rename(f"{EFI_SYS_MOUNT_POINT}/loader/loader.conf.tmp", f"{EFI_SYS_MOUNT_POINT}/loader/loader.conf") + + +def get_bootspec(profile: str | None, generation: int) -> BootSpec: + system_directory = system_dir(profile, generation, None) + boot_json_path = os.path.realpath("%s/%s" % (system_directory, "boot.json")) + if os.path.isfile(boot_json_path): + boot_json_f = open(boot_json_path, 'r') + bootspec_json = json.load(boot_json_f) + else: + boot_json_str = subprocess.check_output([ + f"{BOOTSPEC_TOOLS}/bin/synthesize", + "--version", + "1", + system_directory, + "/dev/stdout"], + universal_newlines=True) + bootspec_json = json.loads(boot_json_str) + return bootspec_from_json(bootspec_json) + +def bootspec_from_json(bootspec_json: Dict) -> BootSpec: + specialisations = bootspec_json['org.nixos.specialisation.v1'] + specialisations = {k: bootspec_from_json(v) for k, v in specialisations.items()} + return BootSpec(**bootspec_json['org.nixos.bootspec.v1'], specialisations=specialisations) + + +def copy_from_file(file: str, dry_run: bool = False) -> str: + store_file_path = os.path.realpath(file) + suffix = os.path.basename(store_file_path) + store_dir = os.path.basename(os.path.dirname(store_file_path)) + efi_file_path = "/efi/nixos/%s-%s.efi" % (store_dir, suffix) + if not dry_run: + copy_if_not_exists(store_file_path, f"{EFI_SYS_MOUNT_POINT}%s" % (efi_file_path)) + return efi_file_path + +def write_entry(profile: str | None, generation: int, specialisation: str | None, + machine_id: str, bootspec: BootSpec, current: bool) -> None: + if specialisation: + bootspec = bootspec.specialisations[specialisation] + kernel = copy_from_file(bootspec.kernel) + initrd = copy_from_file(bootspec.initrd) + + title = "{name}{profile}{specialisation}".format( + name=DISTRO_NAME, + profile=" [" + profile + "]" if profile else "", + specialisation=" (%s)" % specialisation if specialisation else "") + + try: + if bootspec.initrdSecrets is not None: + subprocess.check_call([bootspec.initrdSecrets, f"{EFI_SYS_MOUNT_POINT}%s" % (initrd)]) + except subprocess.CalledProcessError: + if current: + print("failed to create initrd secrets!", file=sys.stderr) + sys.exit(1) + else: + print("warning: failed to create initrd secrets " + f'for "{title} - Configuration {generation}", an older generation', file=sys.stderr) + print("note: this is normal after having removed " + "or renamed a file in `boot.initrd.secrets`", file=sys.stderr) + entry_file = f"{EFI_SYS_MOUNT_POINT}/loader/entries/%s" % ( + generation_conf_filename(profile, generation, specialisation)) + tmp_path = "%s.tmp" % (entry_file) + kernel_params = "init=%s " % bootspec.init + + kernel_params = kernel_params + " ".join(bootspec.kernelParams) + build_time = int(os.path.getctime(system_dir(profile, generation, specialisation))) + build_date = datetime.datetime.fromtimestamp(build_time).strftime('%F') + + with open(tmp_path, 'w') as f: + f.write(BOOT_ENTRY.format(title=title, + generation=generation, + kernel=kernel, + initrd=initrd, + kernel_params=kernel_params, + description=f"{bootspec.label}, built on {build_date}")) + if machine_id is not None: + f.write("machine-id %s\n" % machine_id) + f.flush() + os.fsync(f.fileno()) + os.rename(tmp_path, entry_file) + + +def get_generations(profile: str | None = None) -> list[SystemIdentifier]: + gen_list = subprocess.check_output([ + f"{NIX}/bin/nix-env", + "--list-generations", + "-p", + "/nix/var/nix/profiles/%s" % ("system-profiles/" + profile if profile else "system"), + "--option", "build-users-group", ""], + universal_newlines=True) + gen_lines = gen_list.split('\n') + gen_lines.pop() + + configurationLimit = CONFIGURATION_LIMIT + configurations = [ + SystemIdentifier( + profile=profile, + generation=int(line.split()[0]), + specialisation=None + ) + for line in gen_lines + ] + return configurations[-configurationLimit:] + + +def remove_old_entries(gens: list[SystemIdentifier]) -> None: + rex_profile = re.compile(r"^" + re.escape(EFI_SYS_MOUNT_POINT) + "/loader/entries/nixos-(.*)-generation-.*\.conf$") + rex_generation = re.compile(r"^" + re.escape(EFI_SYS_MOUNT_POINT) + "/loader/entries/nixos.*-generation-([0-9]+)(-specialisation-.*)?\.conf$") + known_paths = [] + for gen in gens: + bootspec = get_bootspec(gen.profile, gen.generation) + known_paths.append(copy_from_file(bootspec.kernel, True)) + known_paths.append(copy_from_file(bootspec.initrd, True)) + for path in glob.iglob(f"{EFI_SYS_MOUNT_POINT}/loader/entries/nixos*-generation-[1-9]*.conf"): + if rex_profile.match(path): + prof = rex_profile.sub(r"\1", path) + else: + prof = None + try: + gen_number = int(rex_generation.sub(r"\1", path)) + except ValueError: + continue + if not (prof, gen_number, None) in gens: + os.unlink(path) + for path in glob.iglob(f"{EFI_SYS_MOUNT_POINT}/efi/nixos/*"): + if not path in known_paths and not os.path.isdir(path): + os.unlink(path) + + +def get_profiles() -> list[str]: + if os.path.isdir("/nix/var/nix/profiles/system-profiles/"): + return [x + for x in os.listdir("/nix/var/nix/profiles/system-profiles/") + if not x.endswith("-link")] + else: + return [] + +def install_bootloader(args: argparse.Namespace) -> None: + try: + with open("/etc/machine-id") as machine_file: + machine_id = machine_file.readlines()[0] + except IOError as e: + if e.errno != errno.ENOENT: + raise + # Since systemd version 232 a machine ID is required and it might not + # be there on newly installed systems, so let's generate one so that + # bootctl can find it and we can also pass it to write_entry() later. + cmd = [f"{SYSTEMD}/bin/systemd-machine-id-setup", "--print"] + machine_id = subprocess.run( + cmd, text=True, check=True, stdout=subprocess.PIPE + ).stdout.rstrip() + + if os.getenv("NIXOS_INSTALL_GRUB") == "1": + warnings.warn("NIXOS_INSTALL_GRUB env var deprecated, use NIXOS_INSTALL_BOOTLOADER", DeprecationWarning) + os.environ["NIXOS_INSTALL_BOOTLOADER"] = "1" + + # flags to pass to bootctl install/update + bootctl_flags = [] + + if CAN_TOUCH_EFI_VARIABLES != "1": + bootctl_flags.append("--no-variables") + + if GRACEFUL == "1": + bootctl_flags.append("--graceful") + + if os.getenv("NIXOS_INSTALL_BOOTLOADER") == "1": + # bootctl uses fopen() with modes "wxe" and fails if the file exists. + if os.path.exists(f"{EFI_SYS_MOUNT_POINT}/loader/loader.conf"): + os.unlink(f"{EFI_SYS_MOUNT_POINT}/loader/loader.conf") + + subprocess.check_call([f"{SYSTEMD}/bin/bootctl", f"--esp-path={EFI_SYS_MOUNT_POINT}"] + bootctl_flags + ["install"]) + else: + # Update bootloader to latest if needed + available_out = subprocess.check_output([f"{SYSTEMD}/bin/bootctl", "--version"], universal_newlines=True).split()[2] + installed_out = subprocess.check_output([f"{SYSTEMD}/bin/bootctl", f"--esp-path={EFI_SYS_MOUNT_POINT}", "status"], universal_newlines=True) + + # See status_binaries() in systemd bootctl.c for code which generates this + installed_match = re.search(r"^\W+File:.*/EFI/(?:BOOT|systemd)/.*\.efi \(systemd-boot ([\d.]+[^)]*)\)$", + installed_out, re.IGNORECASE | re.MULTILINE) + + available_match = re.search(r"^\((.*)\)$", available_out) + + if installed_match is None: + raise Exception("could not find any previously installed systemd-boot") + + if available_match is None: + raise Exception("could not determine systemd-boot version") + + installed_version = installed_match.group(1) + available_version = available_match.group(1) + + if installed_version < available_version: + print("updating systemd-boot from %s to %s" % (installed_version, available_version)) + subprocess.check_call([f"{SYSTEMD}/bin/bootctl", f"--esp-path={EFI_SYS_MOUNT_POINT}"] + bootctl_flags + ["update"]) + + os.makedirs(f"{EFI_SYS_MOUNT_POINT}/efi/nixos", exist_ok=True) + os.makedirs(f"{EFI_SYS_MOUNT_POINT}/loader/entries", exist_ok=True) + + gens = get_generations() + for profile in get_profiles(): + gens += get_generations(profile) + remove_old_entries(gens) + for gen in gens: + try: + bootspec = get_bootspec(gen.profile, gen.generation) + is_default = os.path.dirname(bootspec.init) == args.default_config + write_entry(*gen, machine_id, bootspec, current=is_default) + for specialisation in bootspec.specialisations.keys(): + write_entry(gen.profile, gen.generation, specialisation, machine_id, bootspec, current=is_default) + if is_default: + write_loader_conf(*gen) + except OSError as e: + # See https://github.com/NixOS/nixpkgs/issues/114552 + if e.errno == errno.EINVAL: + profile = f"profile '{gen.profile}'" if gen.profile else "default profile" + print("ignoring {} in the list of boot entries because of the following error:\n{}".format(profile, e), file=sys.stderr) + else: + raise e + + for root, _, files in os.walk(f"{EFI_SYS_MOUNT_POINT}/efi/nixos/.extra-files", topdown=False): + relative_root = root.removeprefix(f"{EFI_SYS_MOUNT_POINT}/efi/nixos/.extra-files").removeprefix("/") + actual_root = os.path.join(f"{EFI_SYS_MOUNT_POINT}", relative_root) + + for file in files: + actual_file = os.path.join(actual_root, file) + + if os.path.exists(actual_file): + os.unlink(actual_file) + os.unlink(os.path.join(root, file)) + + if not len(os.listdir(actual_root)): + os.rmdir(actual_root) + os.rmdir(root) + + os.makedirs(f"{EFI_SYS_MOUNT_POINT}/efi/nixos/.extra-files", exist_ok=True) + + subprocess.check_call(COPY_EXTRA_FILES) + + +def main() -> None: + parser = argparse.ArgumentParser(description=f"Update {DISTRO_NAME}-related systemd-boot files") + parser.add_argument('default_config', metavar='DEFAULT-CONFIG', help=f"The default {DISTRO_NAME} config to boot") + args = parser.parse_args() + + try: + install_bootloader(args) + finally: + # Since fat32 provides little recovery facilities after a crash, + # it can leave the system in an unbootable state, when a crash/outage + # happens shortly after an update. To decrease the likelihood of this + # event sync the efi filesystem after each update. + rc = libc.syncfs(os.open(f"{EFI_SYS_MOUNT_POINT}", os.O_RDONLY)) + if rc != 0: + print(f"could not sync {EFI_SYS_MOUNT_POINT}: {os.strerror(rc)}", file=sys.stderr) + + +if __name__ == '__main__': + main() diff --git a/nixpkgs/nixos/modules/system/boot/loader/systemd-boot/systemd-boot.nix b/nixpkgs/nixos/modules/system/boot/loader/systemd-boot/systemd-boot.nix new file mode 100644 index 000000000000..ea4553b8208f --- /dev/null +++ b/nixpkgs/nixos/modules/system/boot/loader/systemd-boot/systemd-boot.nix @@ -0,0 +1,315 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + cfg = config.boot.loader.systemd-boot; + + efi = config.boot.loader.efi; + + systemdBootBuilder = pkgs.substituteAll { + src = ./systemd-boot-builder.py; + + isExecutable = true; + + inherit (pkgs) python3; + + systemd = config.systemd.package; + + bootspecTools = pkgs.bootspec; + + nix = config.nix.package.out; + + timeout = optionalString (config.boot.loader.timeout != null) config.boot.loader.timeout; + + configurationLimit = if cfg.configurationLimit == null then 0 else cfg.configurationLimit; + + inherit (cfg) consoleMode graceful editor; + + inherit (efi) efiSysMountPoint canTouchEfiVariables; + + inherit (config.system.nixos) distroName; + + memtest86 = optionalString cfg.memtest86.enable pkgs.memtest86plus; + + netbootxyz = optionalString cfg.netbootxyz.enable pkgs.netbootxyz-efi; + + copyExtraFiles = pkgs.writeShellScript "copy-extra-files" '' + empty_file=$(${pkgs.coreutils}/bin/mktemp) + + ${concatStrings (mapAttrsToList (n: v: '' + ${pkgs.coreutils}/bin/install -Dp "${v}" "${efi.efiSysMountPoint}/"${escapeShellArg n} + ${pkgs.coreutils}/bin/install -D $empty_file "${efi.efiSysMountPoint}/efi/nixos/.extra-files/"${escapeShellArg n} + '') cfg.extraFiles)} + + ${concatStrings (mapAttrsToList (n: v: '' + ${pkgs.coreutils}/bin/install -Dp "${pkgs.writeText n v}" "${efi.efiSysMountPoint}/loader/entries/"${escapeShellArg n} + ${pkgs.coreutils}/bin/install -D $empty_file "${efi.efiSysMountPoint}/efi/nixos/.extra-files/loader/entries/"${escapeShellArg n} + '') cfg.extraEntries)} + ''; + }; + + checkedSystemdBootBuilder = pkgs.runCommand "systemd-boot" { + nativeBuildInputs = [ pkgs.mypy ]; + } '' + mkdir -p $out/bin + install -m755 ${systemdBootBuilder} $out/bin/systemd-boot-builder + mypy \ + --no-implicit-optional \ + --disallow-untyped-calls \ + --disallow-untyped-defs \ + $out/bin/systemd-boot-builder + ''; + + finalSystemdBootBuilder = pkgs.writeScript "install-systemd-boot.sh" '' + #!${pkgs.runtimeShell} + ${checkedSystemdBootBuilder}/bin/systemd-boot-builder "$@" + ${cfg.extraInstallCommands} + ''; +in { + + meta.maintainers = with lib.maintainers; [ julienmalka ]; + + imports = + [ (mkRenamedOptionModule [ "boot" "loader" "gummiboot" "enable" ] [ "boot" "loader" "systemd-boot" "enable" ]) + ]; + + options.boot.loader.systemd-boot = { + enable = mkOption { + default = false; + + type = types.bool; + + description = lib.mdDoc '' + Whether to enable the systemd-boot (formerly gummiboot) EFI boot manager. + For more information about systemd-boot: + https://www.freedesktop.org/wiki/Software/systemd/systemd-boot/ + ''; + }; + + editor = mkOption { + default = true; + + type = types.bool; + + description = lib.mdDoc '' + Whether to allow editing the kernel command-line before + boot. It is recommended to set this to false, as it allows + gaining root access by passing init=/bin/sh as a kernel + parameter. However, it is enabled by default for backwards + compatibility. + ''; + }; + + configurationLimit = mkOption { + default = null; + example = 120; + type = types.nullOr types.int; + description = lib.mdDoc '' + Maximum number of latest generations in the boot menu. + Useful to prevent boot partition running out of disk space. + + `null` means no limit i.e. all generations + that were not garbage collected yet. + ''; + }; + + extraInstallCommands = mkOption { + default = ""; + example = '' + default_cfg=$(cat /boot/loader/loader.conf | grep default | awk '{print $2}') + init_value=$(cat /boot/loader/entries/$default_cfg | grep init= | awk '{print $2}') + sed -i "s|@INIT@|$init_value|g" /boot/custom/config_with_placeholder.conf + ''; + type = types.lines; + description = lib.mdDoc '' + Additional shell commands inserted in the bootloader installer + script after generating menu entries. It can be used to expand + on extra boot entries that cannot incorporate certain pieces of + information (such as the resulting `init=` kernel parameter). + ''; + }; + + consoleMode = mkOption { + default = "keep"; + + type = types.enum [ "0" "1" "2" "auto" "max" "keep" ]; + + description = lib.mdDoc '' + The resolution of the console. The following values are valid: + + - `"0"`: Standard UEFI 80x25 mode + - `"1"`: 80x50 mode, not supported by all devices + - `"2"`: The first non-standard mode provided by the device firmware, if any + - `"auto"`: Pick a suitable mode automatically using heuristics + - `"max"`: Pick the highest-numbered available mode + - `"keep"`: Keep the mode selected by firmware (the default) + ''; + }; + + memtest86 = { + enable = mkOption { + default = false; + type = types.bool; + description = lib.mdDoc '' + Make Memtest86+ available from the systemd-boot menu. Memtest86+ is a + program for testing memory. + ''; + }; + + entryFilename = mkOption { + default = "memtest86.conf"; + type = types.str; + description = lib.mdDoc '' + `systemd-boot` orders the menu entries by the config file names, + so if you want something to appear after all the NixOS entries, + it should start with {file}`o` or onwards. + ''; + }; + }; + + netbootxyz = { + enable = mkOption { + default = false; + type = types.bool; + description = lib.mdDoc '' + Make `netboot.xyz` available from the + `systemd-boot` menu. `netboot.xyz` + is a menu system that allows you to boot OS installers and + utilities over the network. + ''; + }; + + entryFilename = mkOption { + default = "o_netbootxyz.conf"; + type = types.str; + description = lib.mdDoc '' + `systemd-boot` orders the menu entries by the config file names, + so if you want something to appear after all the NixOS entries, + it should start with {file}`o` or onwards. + ''; + }; + }; + + extraEntries = mkOption { + type = types.attrsOf types.lines; + default = {}; + example = literalExpression '' + { "memtest86.conf" = ''' + title Memtest86+ + efi /efi/memtest86/memtest.efi + '''; } + ''; + description = lib.mdDoc '' + Any additional entries you want added to the `systemd-boot` menu. + These entries will be copied to {file}`/boot/loader/entries`. + Each attribute name denotes the destination file name, + and the corresponding attribute value is the contents of the entry. + + `systemd-boot` orders the menu entries by the config file names, + so if you want something to appear after all the NixOS entries, + it should start with {file}`o` or onwards. + ''; + }; + + extraFiles = mkOption { + type = types.attrsOf types.path; + default = {}; + example = literalExpression '' + { "efi/memtest86/memtest.efi" = "''${pkgs.memtest86plus}/memtest.efi"; } + ''; + description = lib.mdDoc '' + A set of files to be copied to {file}`/boot`. + Each attribute name denotes the destination file name in + {file}`/boot`, while the corresponding + attribute value specifies the source file. + ''; + }; + + graceful = mkOption { + default = false; + + type = types.bool; + + description = lib.mdDoc '' + Invoke `bootctl install` with the `--graceful` option, + which ignores errors when EFI variables cannot be written or when the EFI System Partition + cannot be found. Currently only applies to random seed operations. + + Only enable this option if `systemd-boot` otherwise fails to install, as the + scope or implication of the `--graceful` option may change in the future. + ''; + }; + + }; + + config = mkIf cfg.enable { + assertions = [ + { + assertion = (config.boot.kernelPackages.kernel.features or { efiBootStub = true; }) ? efiBootStub; + message = "This kernel does not support the EFI boot stub"; + } + ] ++ concatMap (filename: [ + { + assertion = !(hasInfix "/" filename); + message = "boot.loader.systemd-boot.extraEntries.${lib.strings.escapeNixIdentifier filename} is invalid: entries within folders are not supported"; + } + { + assertion = hasSuffix ".conf" filename; + message = "boot.loader.systemd-boot.extraEntries.${lib.strings.escapeNixIdentifier filename} is invalid: entries must have a .conf file extension"; + } + ]) (builtins.attrNames cfg.extraEntries) + ++ concatMap (filename: [ + { + assertion = !(hasPrefix "/" filename); + message = "boot.loader.systemd-boot.extraFiles.${lib.strings.escapeNixIdentifier filename} is invalid: paths must not begin with a slash"; + } + { + assertion = !(hasInfix ".." filename); + message = "boot.loader.systemd-boot.extraFiles.${lib.strings.escapeNixIdentifier filename} is invalid: paths must not reference the parent directory"; + } + { + assertion = !(hasInfix "nixos/.extra-files" (toLower filename)); + message = "boot.loader.systemd-boot.extraFiles.${lib.strings.escapeNixIdentifier filename} is invalid: files cannot be placed in the nixos/.extra-files directory"; + } + ]) (builtins.attrNames cfg.extraFiles); + + boot.loader.grub.enable = mkDefault false; + + boot.loader.supportsInitrdSecrets = true; + + boot.loader.systemd-boot.extraFiles = mkMerge [ + (mkIf cfg.memtest86.enable { + "efi/memtest86/memtest.efi" = "${pkgs.memtest86plus.efi}"; + }) + (mkIf cfg.netbootxyz.enable { + "efi/netbootxyz/netboot.xyz.efi" = "${pkgs.netbootxyz-efi}"; + }) + ]; + + boot.loader.systemd-boot.extraEntries = mkMerge [ + (mkIf cfg.memtest86.enable { + "${cfg.memtest86.entryFilename}" = '' + title Memtest86+ + efi /efi/memtest86/memtest.efi + ''; + }) + (mkIf cfg.netbootxyz.enable { + "${cfg.netbootxyz.entryFilename}" = '' + title netboot.xyz + efi /efi/netbootxyz/netboot.xyz.efi + ''; + }) + ]; + + system = { + build.installBootLoader = finalSystemdBootBuilder; + + boot.loader.id = "systemd-boot"; + + requiredKernelConfig = with config.lib.kernelConfig; [ + (isYes "EFI_STUB") + ]; + }; + }; +} diff --git a/nixpkgs/nixos/modules/system/boot/luksroot.nix b/nixpkgs/nixos/modules/system/boot/luksroot.nix new file mode 100644 index 000000000000..86a3875e2c67 --- /dev/null +++ b/nixpkgs/nixos/modules/system/boot/luksroot.nix @@ -0,0 +1,1130 @@ +{ config, options, lib, utils, pkgs, ... }: + +with lib; + +let + luks = config.boot.initrd.luks; + clevis = config.boot.initrd.clevis; + systemd = config.boot.initrd.systemd; + kernelPackages = config.boot.kernelPackages; + defaultPrio = (mkOptionDefault {}).priority; + + commonFunctions = '' + die() { + echo "$@" >&2 + exit 1 + } + + dev_exist() { + local target="$1" + if [ -e $target ]; then + return 0 + else + local uuid=$(echo -n $target | sed -e 's,UUID=\(.*\),\1,g') + blkid --uuid $uuid >/dev/null + return $? + fi + } + + wait_target() { + local name="$1" + local target="$2" + local secs="''${3:-10}" + local desc="''${4:-$name $target to appear}" + + if ! dev_exist $target; then + echo -n "Waiting $secs seconds for $desc..." + local success=false; + for try in $(seq $secs); do + echo -n "." + sleep 1 + if dev_exist $target; then + success=true + break + fi + done + if [ $success == true ]; then + echo " - success"; + return 0 + else + echo " - failure"; + return 1 + fi + fi + return 0 + } + + wait_yubikey() { + local secs="''${1:-10}" + + ykinfo -v 1>/dev/null 2>&1 + if [ $? != 0 ]; then + echo -n "Waiting $secs seconds for YubiKey to appear..." + local success=false + for try in $(seq $secs); do + echo -n . + sleep 1 + ykinfo -v 1>/dev/null 2>&1 + if [ $? == 0 ]; then + success=true + break + fi + done + if [ $success == true ]; then + echo " - success"; + return 0 + else + echo " - failure"; + return 1 + fi + fi + return 0 + } + + wait_gpgcard() { + local secs="''${1:-10}" + + gpg --card-status > /dev/null 2> /dev/null + if [ $? != 0 ]; then + echo -n "Waiting $secs seconds for GPG Card to appear" + local success=false + for try in $(seq $secs); do + echo -n . + sleep 1 + gpg --card-status > /dev/null 2> /dev/null + if [ $? == 0 ]; then + success=true + break + fi + done + if [ $success == true ]; then + echo " - success"; + return 0 + else + echo " - failure"; + return 1 + fi + fi + return 0 + } + ''; + + preCommands = '' + # A place to store crypto things + + # A ramfs is used here to ensure that the file used to update + # the key slot with cryptsetup will never get swapped out. + # Warning: Do NOT replace with tmpfs! + mkdir -p /crypt-ramfs + mount -t ramfs none /crypt-ramfs + + # Cryptsetup locking directory + mkdir -p /run/cryptsetup + + # For YubiKey salt storage + mkdir -p /crypt-storage + + ${optionalString luks.gpgSupport '' + export GPG_TTY=$(tty) + export GNUPGHOME=/crypt-ramfs/.gnupg + + 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 occasionally leak characters between read + # invocations. + stty -echo + ''; + + postCommands = '' + stty echo + umount /crypt-storage 2>/dev/null + umount /crypt-ramfs 2>/dev/null + ''; + + openCommand = name: dev: assert name == dev.name; + let + csopen = "cryptsetup luksOpen ${dev.device} ${dev.name}" + + optionalString dev.allowDiscards " --allow-discards" + + optionalString dev.bypassWorkqueues " --perf-no_read_workqueue --perf-no_write_workqueue" + + optionalString (dev.header != null) " --header=${dev.header}"; + cschange = "cryptsetup luksChangeKey ${dev.device} ${optionalString (dev.header != null) "--header=${dev.header}"}"; + fido2luksCredentials = dev.fido2.credentials ++ optional (dev.fido2.credential != null) dev.fido2.credential; + in '' + # Wait for luksRoot (and optionally keyFile and/or header) to appear, e.g. + # if on a USB drive. + wait_target "device" ${dev.device} || die "${dev.device} is unavailable" + + ${optionalString (dev.header != null) '' + wait_target "header" ${dev.header} || die "${dev.header} is unavailable" + ''} + + try_empty_passphrase() { + ${if dev.tryEmptyPassphrase then '' + echo "Trying empty passphrase!" + echo "" | ${csopen} + cs_status=$? + if [ $cs_status -eq 0 ]; then + return 0 + else + return 1 + fi + '' else "return 1"} + } + + + do_open_passphrase() { + local passphrase + + while true; do + echo -n "Passphrase for ${dev.device}: " + passphrase= + while true; do + if [ -e /crypt-ramfs/passphrase ]; then + echo "reused" + passphrase=$(cat /crypt-ramfs/passphrase) + break + else + # ask cryptsetup-askpass + echo -n "${dev.device}" > /crypt-ramfs/device + + # and try reading it from /dev/console with a timeout + IFS= read -t 1 -r passphrase + if [ -n "$passphrase" ]; then + ${if luks.reusePassphrases then '' + # remember it for the next device + echo -n "$passphrase" > /crypt-ramfs/passphrase + '' else '' + # Don't save it to ramfs. We are very paranoid + ''} + echo + break + fi + fi + done + echo -n "Verifying passphrase for ${dev.device}..." + echo -n "$passphrase" | ${csopen} --key-file=- + if [ $? == 0 ]; then + echo " - success" + ${if luks.reusePassphrases then '' + # we don't rm here because we might reuse it for the next device + '' else '' + rm -f /crypt-ramfs/passphrase + ''} + break + else + echo " - failure" + # ask for a different one + rm -f /crypt-ramfs/passphrase + fi + done + } + + # LUKS + open_normally() { + ${if (dev.keyFile != null) then '' + if wait_target "key file" ${dev.keyFile}; then + ${csopen} --key-file=${dev.keyFile} \ + ${optionalString (dev.keyFileSize != null) "--keyfile-size=${toString dev.keyFileSize}"} \ + ${optionalString (dev.keyFileOffset != null) "--keyfile-offset=${toString dev.keyFileOffset}"} + cs_status=$? + if [ $cs_status -ne 0 ]; then + echo "Key File ${dev.keyFile} failed!" + if ! try_empty_passphrase; then + ${if dev.fallbackToPassword then "echo" else "die"} "${dev.keyFile} is unavailable" + echo " - failing back to interactive password prompt" + do_open_passphrase + fi + fi + else + # If the key file never shows up we should also try the empty passphrase + if ! try_empty_passphrase; then + ${if dev.fallbackToPassword then "echo" else "die"} "${dev.keyFile} is unavailable" + echo " - failing back to interactive password prompt" + do_open_passphrase + fi + fi + '' else '' + if ! try_empty_passphrase; then + do_open_passphrase + fi + ''} + } + + ${optionalString (luks.yubikeySupport && (dev.yubikey != null)) '' + # YubiKey + rbtohex() { + ( od -An -vtx1 | tr -d ' \n' ) + } + + hextorb() { + ( tr '[:lower:]' '[:upper:]' | sed -e 's/\([0-9A-F]\{2\}\)/\\\\\\x\1/gI' | xargs printf ) + } + + do_open_yubikey() { + # Make all of these local to this function + # to prevent their values being leaked + local salt + local iterations + local k_user + local challenge + local response + local k_luks + local opened + local new_salt + local new_iterations + local new_challenge + local new_response + local new_k_luks + + mount -t ${dev.yubikey.storage.fsType} ${dev.yubikey.storage.device} /crypt-storage || \ + die "Failed to mount YubiKey salt storage device" + + salt="$(cat /crypt-storage${dev.yubikey.storage.path} | sed -n 1p | tr -d '\n')" + iterations="$(cat /crypt-storage${dev.yubikey.storage.path} | sed -n 2p | tr -d '\n')" + challenge="$(echo -n $salt | openssl-wrap dgst -binary -sha512 | rbtohex)" + response="$(ykchalresp -${toString dev.yubikey.slot} -x $challenge 2>/dev/null)" + + for try in $(seq 3); do + ${optionalString dev.yubikey.twoFactor '' + echo -n "Enter two-factor passphrase: " + k_user= + while true; do + if [ -e /crypt-ramfs/passphrase ]; then + echo "reused" + k_user=$(cat /crypt-ramfs/passphrase) + break + else + # Try reading it from /dev/console with a timeout + IFS= read -t 1 -r k_user + if [ -n "$k_user" ]; then + ${if luks.reusePassphrases then '' + # Remember it for the next device + echo -n "$k_user" > /crypt-ramfs/passphrase + '' else '' + # Don't save it to ramfs. We are very paranoid + ''} + echo + break + fi + fi + done + ''} + + if [ ! -z "$k_user" ]; then + k_luks="$(echo -n $k_user | pbkdf2-sha512 ${toString dev.yubikey.keyLength} $iterations $response | rbtohex)" + else + k_luks="$(echo | pbkdf2-sha512 ${toString dev.yubikey.keyLength} $iterations $response | rbtohex)" + fi + + echo -n "$k_luks" | hextorb | ${csopen} --key-file=- + + if [ $? == 0 ]; then + opened=true + ${if luks.reusePassphrases then '' + # We don't rm here because we might reuse it for the next device + '' else '' + rm -f /crypt-ramfs/passphrase + ''} + break + else + opened=false + echo "Authentication failed!" + fi + done + + [ "$opened" == false ] && die "Maximum authentication errors reached" + + echo -n "Gathering entropy for new salt (please enter random keys to generate entropy if this blocks for long)..." + for i in $(seq ${toString dev.yubikey.saltLength}); do + byte="$(dd if=/dev/random bs=1 count=1 2>/dev/null | rbtohex)"; + new_salt="$new_salt$byte"; + echo -n . + done; + echo "ok" + + new_iterations="$iterations" + ${optionalString (dev.yubikey.iterationStep > 0) '' + new_iterations="$(($new_iterations + ${toString dev.yubikey.iterationStep}))" + ''} + + new_challenge="$(echo -n $new_salt | openssl-wrap dgst -binary -sha512 | rbtohex)" + + new_response="$(ykchalresp -${toString dev.yubikey.slot} -x $new_challenge 2>/dev/null)" + + if [ -z "$new_response" ]; then + echo "Warning: Unable to generate new challenge response, current challenge persists!" + umount /crypt-storage + return + fi + + if [ ! -z "$k_user" ]; then + new_k_luks="$(echo -n $k_user | pbkdf2-sha512 ${toString dev.yubikey.keyLength} $new_iterations $new_response | rbtohex)" + else + new_k_luks="$(echo | pbkdf2-sha512 ${toString dev.yubikey.keyLength} $new_iterations $new_response | rbtohex)" + fi + + echo -n "$new_k_luks" | hextorb > /crypt-ramfs/new_key + echo -n "$k_luks" | hextorb | ${cschange} --key-file=- /crypt-ramfs/new_key + + if [ $? == 0 ]; then + echo -ne "$new_salt\n$new_iterations" > /crypt-storage${dev.yubikey.storage.path} + sync /crypt-storage${dev.yubikey.storage.path} + else + echo "Warning: Could not update LUKS key, current challenge persists!" + fi + + rm -f /crypt-ramfs/new_key + umount /crypt-storage + } + + open_with_hardware() { + if wait_yubikey ${toString dev.yubikey.gracePeriod}; then + do_open_yubikey + else + echo "No YubiKey found, falling back to non-YubiKey open procedure" + open_normally + fi + } + ''} + + ${optionalString (luks.gpgSupport && (dev.gpgCard != null)) '' + + do_open_gpg_card() { + # Make all of these local to this function + # to prevent their values being leaked + local pin + local opened + + gpg --import /gpg-keys/${dev.device}/pubkey.asc > /dev/null 2> /dev/null + + gpg --card-status > /dev/null 2> /dev/null + + for try in $(seq 3); do + echo -n "PIN for GPG Card associated with device ${dev.device}: " + pin= + while true; do + if [ -e /crypt-ramfs/passphrase ]; then + echo "reused" + pin=$(cat /crypt-ramfs/passphrase) + break + else + # and try reading it from /dev/console with a timeout + IFS= read -t 1 -r pin + if [ -n "$pin" ]; then + ${if luks.reusePassphrases then '' + # remember it for the next device + echo -n "$pin" > /crypt-ramfs/passphrase + '' else '' + # Don't save it to ramfs. We are very paranoid + ''} + echo + break + fi + fi + done + echo -n "Verifying passphrase for ${dev.device}..." + echo -n "$pin" | gpg -q --batch --passphrase-fd 0 --pinentry-mode loopback -d /gpg-keys/${dev.device}/cryptkey.gpg 2> /dev/null | ${csopen} --key-file=- > /dev/null 2> /dev/null + if [ $? == 0 ]; then + echo " - success" + ${if luks.reusePassphrases then '' + # we don't rm here because we might reuse it for the next device + '' else '' + rm -f /crypt-ramfs/passphrase + ''} + break + else + echo " - failure" + # ask for a different one + rm -f /crypt-ramfs/passphrase + fi + done + + [ "$opened" == false ] && die "Maximum authentication errors reached" + } + + open_with_hardware() { + if wait_gpgcard ${toString dev.gpgCard.gracePeriod}; then + do_open_gpg_card + else + echo "No GPG Card found, falling back to normal open procedure" + open_normally + fi + } + ''} + + ${optionalString (luks.fido2Support && fido2luksCredentials != []) '' + + open_with_hardware() { + local passsphrase + + ${if dev.fido2.passwordLess then '' + export passphrase="" + '' else '' + read -rsp "FIDO2 salt for ${dev.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 open${optionalString dev.allowDiscards " --allow-discards"} ${dev.device} ${dev.name} "${builtins.concatStringsSep "," fido2luksCredentials}" --await-dev ${toString dev.fido2.gracePeriod} --salt string:$passphrase + if [ $? -ne 0 ]; then + echo "No FIDO2 key found, falling back to normal open procedure" + open_normally + fi + } + ''} + + # commands to run right before we mount our device + ${dev.preOpenCommands} + + ${if (luks.yubikeySupport && (dev.yubikey != null)) || (luks.gpgSupport && (dev.gpgCard != null)) || (luks.fido2Support && fido2luksCredentials != []) then '' + open_with_hardware + '' else '' + open_normally + ''} + + # commands to run right after we mounted our device + ${dev.postOpenCommands} + ''; + + askPass = pkgs.writeScriptBin "cryptsetup-askpass" '' + #!/bin/sh + + ${commonFunctions} + + while true; do + wait_target "luks" /crypt-ramfs/device 10 "LUKS to request a passphrase" || die "Passphrase is not requested now" + device=$(cat /crypt-ramfs/device) + + echo -n "Passphrase for $device: " + IFS= read -rs passphrase + echo + + rm /crypt-ramfs/device + echo -n "$passphrase" > /crypt-ramfs/passphrase + done + ''; + + preLVM = filterAttrs (n: v: v.preLVM) luks.devices; + postLVM = filterAttrs (n: v: !v.preLVM) luks.devices; + + + stage1Crypttab = pkgs.writeText "initrd-crypttab" (lib.concatLines (lib.mapAttrsToList (n: v: let + opts = v.crypttabExtraOpts + ++ optional v.allowDiscards "discard" + ++ optionals v.bypassWorkqueues [ "no-read-workqueue" "no-write-workqueue" ] + ++ optional (v.header != null) "header=${v.header}" + ++ optional (v.keyFileOffset != null) "keyfile-offset=${toString v.keyFileOffset}" + ++ optional (v.keyFileSize != null) "keyfile-size=${toString v.keyFileSize}" + ++ optional (v.keyFileTimeout != null) "keyfile-timeout=${builtins.toString v.keyFileTimeout}s" + ++ optional (v.tryEmptyPassphrase) "try-empty-password=true" + ; + in "${n} ${v.device} ${if v.keyFile == null then "-" else v.keyFile} ${lib.concatStringsSep "," opts}") luks.devices)); + +in +{ + imports = [ + (mkRemovedOptionModule [ "boot" "initrd" "luks" "enable" ] "") + ]; + + options = { + + boot.initrd.luks.mitigateDMAAttacks = mkOption { + type = types.bool; + default = true; + description = lib.mdDoc '' + Unless enabled, encryption keys can be easily recovered by an attacker with physical + access to any machine with PCMCIA, ExpressCard, ThunderBolt or FireWire port. + More information is available at <https://en.wikipedia.org/wiki/DMA_attack>. + + This option blacklists FireWire drivers, but doesn't remove them. You can manually + load the drivers if you need to use a FireWire device, but don't forget to unload them! + ''; + }; + + boot.initrd.luks.cryptoModules = mkOption { + type = types.listOf types.str; + default = + [ "aes" "aes_generic" "blowfish" "twofish" + "serpent" "cbc" "xts" "lrw" "sha1" "sha256" "sha512" + "af_alg" "algif_skcipher" + ]; + description = lib.mdDoc '' + A list of cryptographic kernel modules needed to decrypt the root device(s). + The default includes all common modules. + ''; + }; + + boot.initrd.luks.forceLuksSupportInInitrd = mkOption { + type = types.bool; + default = false; + internal = true; + description = lib.mdDoc '' + Whether to configure luks support in the initrd, when no luks + devices are configured. + ''; + }; + + boot.initrd.luks.reusePassphrases = mkOption { + type = types.bool; + default = true; + description = lib.mdDoc '' + When opening a new LUKS device try reusing last successful + passphrase. + + Useful for mounting a number of devices that use the same + passphrase without retyping it several times. + + Such setup can be useful if you use {command}`cryptsetup luksSuspend`. + Different LUKS devices will still have + different master keys even when using the same passphrase. + ''; + }; + + boot.initrd.luks.devices = mkOption { + default = { }; + example = { luksroot.device = "/dev/disk/by-uuid/430e9eff-d852-4f68-aa3b-2fa3599ebe08"; }; + description = lib.mdDoc '' + The encrypted disk that should be opened before the root + filesystem is mounted. Both LVM-over-LUKS and LUKS-over-LVM + setups are supported. The unencrypted devices can be accessed as + {file}`/dev/mapper/«name»`. + ''; + + type = with types; attrsOf (submodule ( + { config, name, ... }: { options = { + + name = mkOption { + visible = false; + default = name; + example = "luksroot"; + type = types.str; + description = lib.mdDoc "Name of the unencrypted device in {file}`/dev/mapper`."; + }; + + device = mkOption { + example = "/dev/disk/by-uuid/430e9eff-d852-4f68-aa3b-2fa3599ebe08"; + type = types.str; + description = lib.mdDoc "Path of the underlying encrypted block device."; + }; + + header = mkOption { + default = null; + example = "/root/header.img"; + type = types.nullOr types.str; + description = lib.mdDoc '' + The name of the file or block device that + should be used as header for the encrypted device. + ''; + }; + + keyFile = mkOption { + default = null; + example = "/dev/sdb1"; + type = types.nullOr types.str; + description = lib.mdDoc '' + The name of the file (can be a raw device or a partition) that + should be used as the decryption key for the encrypted device. If + not specified, you will be prompted for a passphrase instead. + ''; + }; + + tryEmptyPassphrase = mkOption { + default = false; + type = types.bool; + description = lib.mdDoc '' + If keyFile fails then try an empty passphrase first before + prompting for password. + ''; + }; + + keyFileTimeout = mkOption { + default = null; + example = 5; + type = types.nullOr types.int; + description = lib.mdDoc '' + The amount of time in seconds for a keyFile to appear before + timing out and trying passwords. + ''; + }; + + keyFileSize = mkOption { + default = null; + example = 4096; + type = types.nullOr types.int; + description = lib.mdDoc '' + The size of the key file. Use this if only the beginning of the + key file should be used as a key (often the case if a raw device + or partition is used as key file). If not specified, the whole + `keyFile` will be used decryption, instead of just + the first `keyFileSize` bytes. + ''; + }; + + keyFileOffset = mkOption { + default = null; + example = 4096; + type = types.nullOr types.int; + description = lib.mdDoc '' + The offset of the key file. Use this in combination with + `keyFileSize` to use part of a file as key file + (often the case if a raw device or partition is used as a key file). + If not specified, the key begins at the first byte of + `keyFile`. + ''; + }; + + # FIXME: get rid of this option. + preLVM = mkOption { + default = true; + type = types.bool; + description = lib.mdDoc "Whether the luksOpen will be attempted before LVM scan or after it."; + }; + + allowDiscards = mkOption { + default = false; + type = types.bool; + description = lib.mdDoc '' + Whether to allow TRIM requests to the underlying device. This option + has security implications; please read the LUKS documentation before + activating it. + This option is incompatible with authenticated encryption (dm-crypt + stacked over dm-integrity). + ''; + }; + + bypassWorkqueues = mkOption { + default = false; + type = types.bool; + description = lib.mdDoc '' + Whether to bypass dm-crypt's internal read and write workqueues. + Enabling this should improve performance on SSDs; see + [here](https://wiki.archlinux.org/index.php/Dm-crypt/Specialties#Disable_workqueue_for_increased_solid_state_drive_(SSD)_performance) + for more information. Needs Linux 5.9 or later. + ''; + }; + + fallbackToPassword = mkOption { + default = false; + type = types.bool; + description = lib.mdDoc '' + Whether to fallback to interactive passphrase prompt if the keyfile + cannot be found. This will prevent unattended boot should the keyfile + go missing. + ''; + }; + + gpgCard = mkOption { + default = null; + description = lib.mdDoc '' + The option to use this LUKS device with a GPG encrypted luks password by the GPG Smartcard. + If null (the default), GPG-Smartcard will be disabled for this device. + ''; + + type = with types; nullOr (submodule { + options = { + gracePeriod = mkOption { + default = 10; + type = types.int; + description = lib.mdDoc "Time in seconds to wait for the GPG Smartcard."; + }; + + encryptedPass = mkOption { + type = types.path; + description = lib.mdDoc "Path to the GPG encrypted passphrase."; + }; + + publicKey = mkOption { + type = types.path; + description = lib.mdDoc "Path to the Public Key."; + }; + }; + }); + }; + + fido2 = { + credential = mkOption { + default = null; + example = "f1d00200d8dc783f7fb1e10ace8da27f8312d72692abfca2f7e4960a73f48e82e1f7571f6ebfcee9fb434f9886ccc8fcc52a6614d8d2"; + type = types.nullOr types.str; + description = lib.mdDoc "The FIDO2 credential ID."; + }; + + credentials = mkOption { + default = []; + example = [ "f1d00200d8dc783f7fb1e10ace8da27f8312d72692abfca2f7e4960a73f48e82e1f7571f6ebfcee9fb434f9886ccc8fcc52a6614d8d2" ]; + type = types.listOf types.str; + description = lib.mdDoc '' + List of FIDO2 credential IDs. + + Use this if you have multiple FIDO2 keys you want to use for the same luks device. + ''; + }; + + gracePeriod = mkOption { + default = 10; + type = types.int; + description = lib.mdDoc "Time in seconds to wait for the FIDO2 key."; + }; + + passwordLess = mkOption { + default = false; + type = types.bool; + description = lib.mdDoc '' + Defines whatever to use an empty string as a default salt. + + Enable only when your device is PIN protected, such as [Trezor](https://trezor.io/). + ''; + }; + }; + + yubikey = mkOption { + default = null; + description = lib.mdDoc '' + The options to use for this LUKS device in YubiKey-PBA. + If null (the default), YubiKey-PBA will be disabled for this device. + ''; + + type = with types; nullOr (submodule { + options = { + twoFactor = mkOption { + default = true; + type = types.bool; + description = lib.mdDoc "Whether to use a passphrase and a YubiKey (true), or only a YubiKey (false)."; + }; + + slot = mkOption { + default = 2; + type = types.int; + description = lib.mdDoc "Which slot on the YubiKey to challenge."; + }; + + saltLength = mkOption { + default = 16; + type = types.int; + description = lib.mdDoc "Length of the new salt in byte (64 is the effective maximum)."; + }; + + keyLength = mkOption { + default = 64; + type = types.int; + description = lib.mdDoc "Length of the LUKS slot key derived with PBKDF2 in byte."; + }; + + iterationStep = mkOption { + default = 0; + type = types.int; + description = lib.mdDoc "How much the iteration count for PBKDF2 is increased at each successful authentication."; + }; + + gracePeriod = mkOption { + default = 10; + type = types.int; + description = lib.mdDoc "Time in seconds to wait for the YubiKey."; + }; + + /* TODO: Add to the documentation of the current module: + + Options related to the storing the salt. + */ + storage = { + device = mkOption { + default = "/dev/sda1"; + type = types.path; + description = lib.mdDoc '' + An unencrypted device that will temporarily be mounted in stage-1. + Must contain the current salt to create the challenge for this LUKS device. + ''; + }; + + fsType = mkOption { + default = "vfat"; + type = types.str; + description = lib.mdDoc "The filesystem of the unencrypted device."; + }; + + path = mkOption { + default = "/crypt-storage/default"; + type = types.str; + description = lib.mdDoc '' + Absolute path of the salt on the unencrypted device with + that device's root directory as "/". + ''; + }; + }; + }; + }); + }; + + preOpenCommands = mkOption { + type = types.lines; + default = ""; + example = '' + mkdir -p /tmp/persistent + mount -t zfs rpool/safe/persistent /tmp/persistent + ''; + description = lib.mdDoc '' + Commands that should be run right before we try to mount our LUKS device. + This can be useful, if the keys needed to open the drive is on another partition. + ''; + }; + + postOpenCommands = mkOption { + type = types.lines; + default = ""; + example = '' + umount /tmp/persistent + ''; + description = lib.mdDoc '' + Commands that should be run right after we have mounted our LUKS device. + ''; + }; + + crypttabExtraOpts = mkOption { + type = with types; listOf singleLineStr; + default = []; + example = [ "_netdev" ]; + visible = false; + description = lib.mdDoc '' + Only used with systemd stage 1. + + Extra options to append to the last column of the generated crypttab file. + ''; + }; + }; + + config = mkIf (clevis.enable && (hasAttr name clevis.devices)) { + preOpenCommands = mkIf (!systemd.enable) '' + mkdir -p /clevis-${name} + mount -t ramfs none /clevis-${name} + clevis decrypt < /etc/clevis/${name}.jwe > /clevis-${name}/decrypted + ''; + keyFile = "/clevis-${name}/decrypted"; + fallbackToPassword = !systemd.enable; + postOpenCommands = mkIf (!systemd.enable) '' + umount /clevis-${name} + ''; + }; + })); + }; + + boot.initrd.luks.gpgSupport = mkOption { + default = false; + type = types.bool; + description = lib.mdDoc '' + Enables support for authenticating with a GPG encrypted password. + ''; + }; + + boot.initrd.luks.yubikeySupport = mkOption { + default = false; + type = types.bool; + description = lib.mdDoc '' + Enables support for authenticating with a YubiKey on LUKS devices. + See the NixOS wiki for information on how to properly setup a LUKS device + and a YubiKey to work with this feature. + ''; + }; + + boot.initrd.luks.fido2Support = mkOption { + default = false; + type = types.bool; + description = lib.mdDoc '' + Enables support for authenticating with FIDO2 devices. + ''; + }; + + }; + + config = mkIf (luks.devices != {} || luks.forceLuksSupportInInitrd) { + + assertions = + [ { 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."; + } + + { assertion = any (dev: dev.bypassWorkqueues) (attrValues luks.devices) + -> versionAtLeast kernelPackages.kernel.version "5.9"; + message = "boot.initrd.luks.devices.<name>.bypassWorkqueues is not supported for kernels older than 5.9"; + } + + { assertion = !config.boot.initrd.systemd.enable -> all (x: x.keyFileTimeout == null) (attrValues luks.devices); + message = "boot.initrd.luks.devices.<name>.keyFileTimeout is only supported for systemd initrd"; + } + + { assertion = config.boot.initrd.systemd.enable -> all (dev: !dev.fallbackToPassword) (attrValues luks.devices); + message = "boot.initrd.luks.devices.<name>.fallbackToPassword is implied by systemd stage 1."; + } + { assertion = config.boot.initrd.systemd.enable -> all (dev: dev.preLVM) (attrValues luks.devices); + message = "boot.initrd.luks.devices.<name>.preLVM is not used by systemd stage 1."; + } + { assertion = config.boot.initrd.systemd.enable -> options.boot.initrd.luks.reusePassphrases.highestPrio == defaultPrio; + message = "boot.initrd.luks.reusePassphrases has no effect with systemd stage 1."; + } + { assertion = config.boot.initrd.systemd.enable -> all (dev: dev.preOpenCommands == "" && dev.postOpenCommands == "") (attrValues luks.devices); + message = "boot.initrd.luks.devices.<name>.preOpenCommands and postOpenCommands is not supported by systemd stage 1. Please bind a service to cryptsetup.target or cryptsetup-pre.target instead."; + } + # TODO + { assertion = config.boot.initrd.systemd.enable -> !luks.gpgSupport; + message = "systemd stage 1 does not support GPG smartcards yet."; + } + { assertion = config.boot.initrd.systemd.enable -> !luks.fido2Support; + message = '' + systemd stage 1 does not support configuring FIDO2 unlocking through `boot.initrd.luks.devices.<name>.fido2`. + Use systemd-cryptenroll(1) to configure FIDO2 support. + ''; + } + # TODO + { assertion = config.boot.initrd.systemd.enable -> !luks.yubikeySupport; + message = "systemd stage 1 does not support Yubikeys yet."; + } + ]; + + # actually, sbp2 driver is the one enabling the DMA attack, but this needs to be tested + boot.blacklistedKernelModules = optionals luks.mitigateDMAAttacks + ["firewire_ohci" "firewire_core" "firewire_sbp2"]; + + # Some modules that may be needed for mounting anything ciphered + boot.initrd.availableKernelModules = [ "dm_mod" "dm_crypt" "cryptd" "input_leds" ] + ++ luks.cryptoModules + # workaround until https://marc.info/?l=linux-crypto-vger&m=148783562211457&w=4 is merged + # remove once 'modprobe --show-depends xts' shows ecb as a dependency + ++ (optional (builtins.elem "xts" luks.cryptoModules) "ecb"); + + # copy the cryptsetup binary and it's dependencies + boot.initrd.extraUtilsCommands = let + pbkdf2-sha512 = pkgs.runCommandCC "pbkdf2-sha512" { buildInputs = [ pkgs.openssl ]; } '' + mkdir -p "$out/bin" + cc -O3 -lcrypto ${./pbkdf2-sha512.c} -o "$out/bin/pbkdf2-sha512" + strip -s "$out/bin/pbkdf2-sha512" + ''; + in + mkIf (!config.boot.initrd.systemd.enable) '' + copy_bin_and_libs ${pkgs.cryptsetup}/bin/cryptsetup + copy_bin_and_libs ${askPass}/bin/cryptsetup-askpass + sed -i s,/bin/sh,$out/bin/sh, $out/bin/cryptsetup-askpass + + ${optionalString luks.yubikeySupport '' + copy_bin_and_libs ${pkgs.yubikey-personalization}/bin/ykchalresp + copy_bin_and_libs ${pkgs.yubikey-personalization}/bin/ykinfo + copy_bin_and_libs ${pkgs.openssl.bin}/bin/openssl + + copy_bin_and_libs ${pbkdf2-sha512}/bin/pbkdf2-sha512 + + mkdir -p $out/etc/ssl + cp -pdv ${pkgs.openssl.out}/etc/ssl/openssl.cnf $out/etc/ssl + + cat > $out/bin/openssl-wrap <<EOF + #!$out/bin/sh + export OPENSSL_CONF=$out/etc/ssl/openssl.cnf + $out/bin/openssl "\$@" + EOF + 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 + copy_bin_and_libs ${pkgs.gnupg}/libexec/scdaemon + + ${concatMapStringsSep "\n" (x: + optionalString (x.gpgCard != null) + '' + mkdir -p $out/secrets/gpg-keys/${x.device} + cp -a ${x.gpgCard.encryptedPass} $out/secrets/gpg-keys/${x.device}/cryptkey.gpg + cp -a ${x.gpgCard.publicKey} $out/secrets/gpg-keys/${x.device}/pubkey.asc + '' + ) (attrValues luks.devices) + } + ''} + ''; + + boot.initrd.extraUtilsCommandsTest = mkIf (!config.boot.initrd.systemd.enable) '' + $out/bin/cryptsetup --version + ${optionalString luks.yubikeySupport '' + $out/bin/ykchalresp -V + $out/bin/ykinfo -V + $out/bin/openssl-wrap version + ''} + ${optionalString luks.gpgSupport '' + $out/bin/gpg --version + $out/bin/gpg-agent --version + $out/bin/scdaemon --version + ''} + ${optionalString luks.fido2Support '' + $out/bin/fido2luks --version + ''} + ''; + + boot.initrd.systemd = { + contents."/etc/crypttab".source = stage1Crypttab; + + extraBin.systemd-cryptsetup = "${config.boot.initrd.systemd.package}/bin/systemd-cryptsetup"; + + additionalUpstreamUnits = [ + "cryptsetup-pre.target" + "cryptsetup.target" + "remote-cryptsetup.target" + ]; + storePaths = [ + "${config.boot.initrd.systemd.package}/bin/systemd-cryptsetup" + "${config.boot.initrd.systemd.package}/lib/systemd/system-generators/systemd-cryptsetup-generator" + ]; + + }; + # We do this because we need the udev rules from the package + boot.initrd.services.lvm.enable = true; + + boot.initrd.preFailCommands = mkIf (!config.boot.initrd.systemd.enable) postCommands; + boot.initrd.preLVMCommands = mkIf (!config.boot.initrd.systemd.enable) (commonFunctions + preCommands + concatStrings (mapAttrsToList openCommand preLVM) + postCommands); + boot.initrd.postDeviceCommands = mkIf (!config.boot.initrd.systemd.enable) (commonFunctions + preCommands + concatStrings (mapAttrsToList openCommand postLVM) + postCommands); + + boot.initrd.systemd.services = let devicesWithClevis = filterAttrs (device: _: (hasAttr device clevis.devices)) luks.devices; in + mkIf (clevis.enable && systemd.enable) ( + (mapAttrs' + (name: _: nameValuePair "cryptsetup-clevis-${name}" { + wantedBy = [ "systemd-cryptsetup@${utils.escapeSystemdPath name}.service" ]; + before = [ + "systemd-cryptsetup@${utils.escapeSystemdPath name}.service" + "initrd-switch-root.target" + "shutdown.target" + ]; + wants = [ "systemd-udev-settle.service" ] ++ optional clevis.useTang "network-online.target"; + after = [ "systemd-modules-load.service" "systemd-udev-settle.service" ] ++ optional clevis.useTang "network-online.target"; + script = '' + mkdir -p /clevis-${name} + mount -t ramfs none /clevis-${name} + umask 277 + clevis decrypt < /etc/clevis/${name}.jwe > /clevis-${name}/decrypted + ''; + conflicts = [ "initrd-switch-root.target" "shutdown.target" ]; + unitConfig.DefaultDependencies = "no"; + serviceConfig = { + Type = "oneshot"; + RemainAfterExit = true; + ExecStop = "${config.boot.initrd.systemd.package.util-linux}/bin/umount /clevis-${name}"; + }; + }) + devicesWithClevis) + ); + + environment.systemPackages = [ pkgs.cryptsetup ]; + }; +} diff --git a/nixpkgs/nixos/modules/system/boot/modprobe.nix b/nixpkgs/nixos/modules/system/boot/modprobe.nix new file mode 100644 index 000000000000..d751c4462d3f --- /dev/null +++ b/nixpkgs/nixos/modules/system/boot/modprobe.nix @@ -0,0 +1,72 @@ +{ config, lib, pkgs, ... }: + +with lib; + +{ + + ###### interface + + options = { + boot.modprobeConfig.enable = mkEnableOption (lib.mdDoc "modprobe config. This is useful for systems like containers which do not require a kernel") // { + default = true; + }; + + boot.blacklistedKernelModules = mkOption { + type = types.listOf types.str; + default = []; + example = [ "cirrusfb" "i2c_piix4" ]; + description = lib.mdDoc '' + List of names of kernel modules that should not be loaded + automatically by the hardware probing code. + ''; + }; + + boot.extraModprobeConfig = mkOption { + default = ""; + example = + '' + options parport_pc io=0x378 irq=7 dma=1 + ''; + description = lib.mdDoc '' + Any additional configuration to be appended to the generated + {file}`modprobe.conf`. This is typically used to + specify module options. See + {manpage}`modprobe.d(5)` for details. + ''; + type = types.lines; + }; + + }; + + + ###### implementation + + config = mkIf config.boot.modprobeConfig.enable { + + environment.etc."modprobe.d/ubuntu.conf".source = "${pkgs.kmod-blacklist-ubuntu}/modprobe.conf"; + + environment.etc."modprobe.d/nixos.conf".text = + '' + ${flip concatMapStrings config.boot.blacklistedKernelModules (name: '' + blacklist ${name} + '')} + ${config.boot.extraModprobeConfig} + ''; + environment.etc."modprobe.d/debian.conf".source = pkgs.kmod-debian-aliases; + + environment.etc."modprobe.d/systemd.conf".source = "${config.systemd.package}/lib/modprobe.d/systemd.conf"; + + environment.systemPackages = [ pkgs.kmod ]; + + system.activationScripts.modprobe = stringAfter ["specialfs"] + '' + # Allow the kernel to find our wrapped modprobe (which searches + # in the right location in the Nix store for kernel modules). + # We need this when the kernel (or some module) auto-loads a + # module. + echo ${pkgs.kmod}/bin/modprobe > /proc/sys/kernel/modprobe + ''; + + }; + +} diff --git a/nixpkgs/nixos/modules/system/boot/networkd.nix b/nixpkgs/nixos/modules/system/boot/networkd.nix new file mode 100644 index 000000000000..a7399bd55e77 --- /dev/null +++ b/nixpkgs/nixos/modules/system/boot/networkd.nix @@ -0,0 +1,3033 @@ +{ config, lib, pkgs, utils, ... }: + +with utils.systemdUtils.unitOptions; +with utils.systemdUtils.lib; +with utils.systemdUtils.network.units; +with lib; + +let + + check = { + + global = { + sectionNetwork = checkUnitConfig "Network" [ + (assertOnlyFields [ + "SpeedMeter" + "SpeedMeterIntervalSec" + "ManageForeignRoutingPolicyRules" + "ManageForeignRoutes" + "RouteTable" + ]) + (assertValueOneOf "SpeedMeter" boolValues) + (assertInt "SpeedMeterIntervalSec") + (assertValueOneOf "ManageForeignRoutingPolicyRules" boolValues) + (assertValueOneOf "ManageForeignRoutes" boolValues) + ]; + + sectionDHCPv4 = checkUnitConfig "DHCPv4" [ + (assertOnlyFields [ + "ClientIdentifier" + "DUIDType" + "DUIDRawData" + ]) + (assertValueOneOf "ClientIdentifier" ["mac" "duid" "duid-only"]) + ]; + + sectionDHCPv6 = checkUnitConfig "DHCPv6" [ + (assertOnlyFields [ + "DUIDType" + "DUIDRawData" + ]) + ]; + }; + + link = { + + sectionLink = checkUnitConfig "Link" [ + (assertOnlyFields [ + "Description" + "Alias" + "MACAddressPolicy" + "MACAddress" + "NamePolicy" + "Name" + "AlternativeNamesPolicy" + "AlternativeName" + "MTUBytes" + "BitsPerSecond" + "Duplex" + "AutoNegotiation" + "WakeOnLan" + "Port" + "Advertise" + "ReceiveChecksumOffload" + "TransmitChecksumOffload" + "TCPSegmentationOffload" + "TCP6SegmentationOffload" + "GenericSegmentationOffload" + "GenericReceiveOffload" + "LargeReceiveOffload" + "RxChannels" + "TxChannels" + "OtherChannels" + "CombinedChannels" + "RxBufferSize" + "TxBufferSize" + "ReceiveQueues" + "TransmitQueues" + "TransmitQueueLength" + ]) + (assertValueOneOf "MACAddressPolicy" ["persistent" "random" "none"]) + (assertMacAddress "MACAddress") + (assertByteFormat "MTUBytes") + (assertByteFormat "BitsPerSecond") + (assertValueOneOf "Duplex" ["half" "full"]) + (assertValueOneOf "AutoNegotiation" boolValues) + (assertValuesSomeOfOr "WakeOnLan" ["phy" "unicast" "multicast" "broadcast" "arp" "magic" "secureon"] "off") + (assertValueOneOf "Port" ["tp" "aui" "bnc" "mii" "fibre"]) + (assertValueOneOf "ReceiveChecksumOffload" boolValues) + (assertValueOneOf "TransmitChecksumOffload" boolValues) + (assertValueOneOf "TCPSegmentationOffload" boolValues) + (assertValueOneOf "TCP6SegmentationOffload" boolValues) + (assertValueOneOf "GenericSegmentationOffload" boolValues) + (assertValueOneOf "GenericReceiveOffload" boolValues) + (assertValueOneOf "LargeReceiveOffload" boolValues) + (assertInt "RxChannels") + (assertRange "RxChannels" 1 4294967295) + (assertInt "TxChannels") + (assertRange "TxChannels" 1 4294967295) + (assertInt "OtherChannels") + (assertRange "OtherChannels" 1 4294967295) + (assertInt "CombinedChannels") + (assertRange "CombinedChannels" 1 4294967295) + (assertInt "RxBufferSize") + (assertInt "TxBufferSize") + (assertRange "ReceiveQueues" 1 4096) + (assertRange "TransmitQueues" 1 4096) + (assertRange "TransmitQueueLength" 1 4294967294) + ]; + }; + + netdev = let + + tunChecks = [ + (assertOnlyFields [ + "MultiQueue" + "PacketInfo" + "VNetHeader" + "User" + "Group" + ]) + (assertValueOneOf "MultiQueue" boolValues) + (assertValueOneOf "PacketInfo" boolValues) + (assertValueOneOf "VNetHeader" boolValues) + ]; + + # See https://www.freedesktop.org/software/systemd/man/latest/systemd.netdev.html#%5BIPVTAP%5D%20Section%20Options + ipVlanVtapChecks = [ + (assertOnlyFields [ + "Mode" + "Flags" + ]) + (assertValueOneOf "Mode" ["L2" "L3" "L3S" ]) + (assertValueOneOf "Flags" ["private" "vepa" "bridge" ]) + ]; + in { + + sectionNetdev = checkUnitConfig "Netdev" [ + (assertOnlyFields [ + "Description" + "Name" + "Kind" + "MTUBytes" + "MACAddress" + ]) + (assertHasField "Name") + (assertHasField "Kind") + (assertValueOneOf "Kind" [ + "bond" + "bridge" + "dummy" + "gre" + "gretap" + "erspan" + "ip6gre" + "ip6tnl" + "ip6gretap" + "ipip" + "ipvlan" + "ipvtap" + "macvlan" + "macvtap" + "sit" + "tap" + "tun" + "veth" + "vlan" + "vti" + "vti6" + "vxlan" + "geneve" + "l2tp" + "macsec" + "wlan" + "vrf" + "vcan" + "vxcan" + "wireguard" + "netdevsim" + "nlmon" + "fou" + "xfrm" + "ifb" + "batadv" + ]) + (assertByteFormat "MTUBytes") + (assertNetdevMacAddress "MACAddress") + ]; + + sectionVLAN = checkUnitConfig "VLAN" [ + (assertOnlyFields [ + "Id" + "GVRP" + "MVRP" + "LooseBinding" + "ReorderHeader" + ]) + (assertInt "Id") + (assertRange "Id" 0 4094) + (assertValueOneOf "GVRP" boolValues) + (assertValueOneOf "MVRP" boolValues) + (assertValueOneOf "LooseBinding" boolValues) + (assertValueOneOf "ReorderHeader" boolValues) + ]; + + sectionIPVLAN = checkUnitConfig "IPVLAN" ipVlanVtapChecks; + + sectionIPVTAP = checkUnitConfig "IPVTAP" ipVlanVtapChecks; + + sectionMACVLAN = checkUnitConfig "MACVLAN" [ + (assertOnlyFields [ + "Mode" + ]) + (assertValueOneOf "Mode" ["private" "vepa" "bridge" "passthru"]) + ]; + + sectionVXLAN = checkUnitConfig "VXLAN" [ + (assertOnlyFields [ + "VNI" + "Remote" + "Local" + "Group" + "TOS" + "TTL" + "MacLearning" + "FDBAgeingSec" + "MaximumFDBEntries" + "ReduceARPProxy" + "L2MissNotification" + "L3MissNotification" + "RouteShortCircuit" + "UDPChecksum" + "UDP6ZeroChecksumTx" + "UDP6ZeroChecksumRx" + "RemoteChecksumTx" + "RemoteChecksumRx" + "GroupPolicyExtension" + "GenericProtocolExtension" + "DestinationPort" + "PortRange" + "FlowLabel" + "IPDoNotFragment" + "Independent" + ]) + (assertInt "VNI") + (assertRange "VNI" 1 16777215) + (assertValueOneOf "MacLearning" boolValues) + (assertInt "MaximumFDBEntries") + (assertValueOneOf "ReduceARPProxy" boolValues) + (assertValueOneOf "L2MissNotification" boolValues) + (assertValueOneOf "L3MissNotification" boolValues) + (assertValueOneOf "RouteShortCircuit" boolValues) + (assertValueOneOf "UDPChecksum" boolValues) + (assertValueOneOf "UDP6ZeroChecksumTx" boolValues) + (assertValueOneOf "UDP6ZeroChecksumRx" boolValues) + (assertValueOneOf "RemoteChecksumTx" boolValues) + (assertValueOneOf "RemoteChecksumRx" boolValues) + (assertValueOneOf "GroupPolicyExtension" boolValues) + (assertValueOneOf "GenericProtocolExtension" boolValues) + (assertInt "FlowLabel") + (assertRange "FlowLabel" 0 1048575) + (assertValueOneOf "IPDoNotFragment" (boolValues + ["inherit"])) + (assertValueOneOf "Independent" boolValues) + ]; + + sectionTunnel = checkUnitConfig "Tunnel" [ + (assertOnlyFields [ + "Local" + "Remote" + "TOS" + "TTL" + "DiscoverPathMTU" + "IPv6FlowLabel" + "CopyDSCP" + "EncapsulationLimit" + "Key" + "InputKey" + "OutputKey" + "Mode" + "Independent" + "AssignToLoopback" + "AllowLocalRemote" + "FooOverUDP" + "FOUDestinationPort" + "FOUSourcePort" + "Encapsulation" + "IPv6RapidDeploymentPrefix" + "ISATAP" + "SerializeTunneledPackets" + "ERSPANIndex" + ]) + (assertInt "TTL") + (assertRange "TTL" 0 255) + (assertValueOneOf "DiscoverPathMTU" boolValues) + (assertValueOneOf "CopyDSCP" boolValues) + (assertValueOneOf "Mode" ["ip6ip6" "ipip6" "any"]) + (assertValueOneOf "Independent" boolValues) + (assertValueOneOf "AssignToLoopback" boolValues) + (assertValueOneOf "AllowLocalRemote" boolValues) + (assertValueOneOf "FooOverUDP" boolValues) + (assertPort "FOUDestinationPort") + (assertPort "FOUSourcePort") + (assertValueOneOf "Encapsulation" ["FooOverUDP" "GenericUDPEncapsulation"]) + (assertValueOneOf "ISATAP" boolValues) + (assertValueOneOf "SerializeTunneledPackets" boolValues) + (assertInt "ERSPANIndex") + (assertRange "ERSPANIndex" 1 1048575) + ]; + + sectionFooOverUDP = checkUnitConfig "FooOverUDP" [ + (assertOnlyFields [ + "Port" + "Encapsulation" + "Protocol" + ]) + (assertPort "Port") + (assertValueOneOf "Encapsulation" ["FooOverUDP" "GenericUDPEncapsulation"]) + ]; + + sectionPeer = checkUnitConfig "Peer" [ + (assertOnlyFields [ + "Name" + "MACAddress" + ]) + (assertMacAddress "MACAddress") + ]; + + sectionTun = checkUnitConfig "Tun" tunChecks; + + sectionTap = checkUnitConfig "Tap" tunChecks; + + sectionL2TP = checkUnitConfig "L2TP" [ + (assertOnlyFields [ + "TunnelId" + "PeerTunnelId" + "Remote" + "Local" + "EncapsulationType" + "UDPSourcePort" + "UDPDestinationPort" + "UDPChecksum" + "UDP6ZeroChecksumTx" + "UDP6ZeroChecksumRx" + ]) + (assertInt "TunnelId") + (assertRange "TunnelId" 1 4294967295) + (assertInt "PeerTunnelId") + (assertRange "PeerTunnelId" 1 4294967295) + (assertValueOneOf "EncapsulationType" [ "ip" "udp" ]) + (assertPort "UDPSourcePort") + (assertPort "UDPDestinationPort") + (assertValueOneOf "UDPChecksum" boolValues) + (assertValueOneOf "UDP6ZeroChecksumTx" boolValues) + (assertValueOneOf "UDP6ZeroChecksumRx" boolValues) + ]; + + sectionL2TPSession = checkUnitConfig "L2TPSession" [ + (assertOnlyFields [ + "Name" + "SessionId" + "PeerSessionId" + "Layer2SpecificHeader" + ]) + (assertHasField "Name") + (assertHasField "SessionId") + (assertInt "SessionId") + (assertRange "SessionId" 1 4294967295) + (assertHasField "PeerSessionId") + (assertInt "PeerSessionId") + (assertRange "PeerSessionId" 1 4294967295) + (assertValueOneOf "Layer2SpecificHeader" [ "none" "default" ]) + ]; + + # 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. + sectionWireGuard = checkUnitConfig "WireGuard" [ + (assertOnlyFields [ + "PrivateKeyFile" + "ListenPort" + "FirewallMark" + "RouteTable" + "RouteMetric" + ]) + (assertInt "FirewallMark") + (assertRange "FirewallMark" 1 4294967295) + ]; + + # NOTE The PresharedKey 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. + sectionWireGuardPeer = checkUnitConfig "WireGuardPeer" [ + (assertOnlyFields [ + "PublicKey" + "PresharedKeyFile" + "AllowedIPs" + "Endpoint" + "PersistentKeepalive" + "RouteTable" + "RouteMetric" + ]) + (assertInt "PersistentKeepalive") + (assertRange "PersistentKeepalive" 0 65535) + ]; + + sectionBond = checkUnitConfig "Bond" [ + (assertOnlyFields [ + "Mode" + "TransmitHashPolicy" + "LACPTransmitRate" + "MIIMonitorSec" + "UpDelaySec" + "DownDelaySec" + "LearnPacketIntervalSec" + "AdSelect" + "AdActorSystemPriority" + "AdUserPortKey" + "AdActorSystem" + "FailOverMACPolicy" + "ARPValidate" + "ARPIntervalSec" + "ARPIPTargets" + "ARPAllTargets" + "PrimaryReselectPolicy" + "ResendIGMP" + "PacketsPerSlave" + "GratuitousARP" + "AllSlavesActive" + "DynamicTransmitLoadBalancing" + "MinLinks" + ]) + (assertValueOneOf "Mode" [ + "balance-rr" + "active-backup" + "balance-xor" + "broadcast" + "802.3ad" + "balance-tlb" + "balance-alb" + ]) + (assertValueOneOf "TransmitHashPolicy" [ + "layer2" + "layer3+4" + "layer2+3" + "encap2+3" + "encap3+4" + ]) + (assertValueOneOf "LACPTransmitRate" ["slow" "fast"]) + (assertValueOneOf "AdSelect" ["stable" "bandwidth" "count"]) + (assertInt "AdActorSystemPriority") + (assertRange "AdActorSystemPriority" 1 65535) + (assertInt "AdUserPortKey") + (assertRange "AdUserPortKey" 0 1023) + (assertValueOneOf "FailOverMACPolicy" ["none" "active" "follow"]) + (assertValueOneOf "ARPValidate" ["none" "active" "backup" "all"]) + (assertValueOneOf "ARPAllTargets" ["any" "all"]) + (assertValueOneOf "PrimaryReselectPolicy" ["always" "better" "failure"]) + (assertInt "ResendIGMP") + (assertRange "ResendIGMP" 0 255) + (assertInt "PacketsPerSlave") + (assertRange "PacketsPerSlave" 0 65535) + (assertInt "GratuitousARP") + (assertRange "GratuitousARP" 0 255) + (assertValueOneOf "AllSlavesActive" boolValues) + (assertValueOneOf "DynamicTransmitLoadBalancing" boolValues) + (assertInt "MinLinks") + (assertMinimum "MinLinks" 0) + ]; + + sectionXfrm = checkUnitConfig "Xfrm" [ + (assertOnlyFields [ + "InterfaceId" + "Independent" + ]) + (assertInt "InterfaceId") + (assertRange "InterfaceId" 1 4294967295) + (assertValueOneOf "Independent" boolValues) + ]; + + sectionVRF = checkUnitConfig "VRF" [ + (assertOnlyFields [ + "Table" + ]) + (assertInt "Table") + (assertMinimum "Table" 0) + ]; + + sectionWLAN = checkUnitConfig "WLAN" [ + (assertOnlyFields [ + "PhysicalDevice" # systemd supports both strings ("phy0") and indexes (0) here. + "Type" + "WDS" + ]) + # See https://github.com/systemd/systemd/blob/main/src/basic/linux/nl80211.h#L3382 + (assertValueOneOf "Type" [ + "ad-hoc" + "station" + "ap" + "ap-vlan" + "wds" + "monitor" + "mesh-point" + "p2p-client" + "p2p-go" + "p2p-device" + "ocb" + "nan" + ]) + (assertValueOneOf "WDS" boolValues) + ]; + + sectionBatmanAdvanced = checkUnitConfig "BatmanAdvanced" [ + (assertOnlyFields [ + "GatewayMode" + "Aggregation" + "BridgeLoopAvoidance" + "DistributedArpTable" + "Fragmentation" + "HopPenalty" + "OriginatorIntervalSec" + "GatewayBandwithDown" + "GatewayBandwithUp" + "RoutingAlgorithm" + ]) + (assertValueOneOf "GatewayMode" ["off" "client" "server"]) + (assertValueOneOf "Aggregation" boolValues) + (assertValueOneOf "BridgeLoopAvoidance" boolValues) + (assertValueOneOf "DistributedArpTable" boolValues) + (assertValueOneOf "Fragmentation" boolValues) + (assertInt "HopPenalty") + (assertRange "HopPenalty" 0 255) + (assertValueOneOf "RoutingAlgorithm" ["batman-v" "batman-iv"]) + ]; + }; + + network = { + + sectionLink = checkUnitConfig "Link" [ + (assertOnlyFields [ + "MACAddress" + "MTUBytes" + "ARP" + "Multicast" + "AllMulticast" + "Unmanaged" + "Group" + "RequiredForOnline" + "RequiredFamilyForOnline" + "ActivationPolicy" + "Promiscuous" + ]) + (assertMacAddress "MACAddress") + (assertByteFormat "MTUBytes") + (assertValueOneOf "ARP" boolValues) + (assertValueOneOf "Multicast" boolValues) + (assertValueOneOf "AllMulticast" boolValues) + (assertValueOneOf "Promiscuous" boolValues) + (assertValueOneOf "Unmanaged" boolValues) + (assertInt "Group") + (assertRange "Group" 0 2147483647) + (assertValueOneOf "RequiredForOnline" (boolValues ++ ( + let + # https://freedesktop.org/software/systemd/man/networkctl.html#missing + operationalStates = [ + "missing" + "off" + "no-carrier" + "dormant" + "degraded-carrier" + "carrier" + "degraded" + "enslaved" + "routable" + ]; + operationalStateRanges = concatLists (imap0 (i: min: map (max: "${min}:${max}") (drop i operationalStates)) operationalStates); + in + operationalStates ++ operationalStateRanges + ))) + (assertValueOneOf "RequiredFamilyForOnline" [ + "ipv4" + "ipv6" + "both" + "any" + ]) + (assertValueOneOf "ActivationPolicy" ([ + "up" + "always-up" + "manual" + "always-down" + "down" + "bound" + ])) + ]; + + sectionNetwork = checkUnitConfig "Network" [ + (assertOnlyFields [ + "Description" + "DHCP" + "DHCPServer" + "LinkLocalAddressing" + "IPv6LinkLocalAddressGenerationMode" + "IPv6StableSecretAddress" + "IPv4LLRoute" + "DefaultRouteOnDevice" + "LLMNR" + "MulticastDNS" + "DNSOverTLS" + "DNSSEC" + "DNSSECNegativeTrustAnchors" + "LLDP" + "EmitLLDP" + "BindCarrier" + "Address" + "Gateway" + "DNS" + "Domains" + "DNSDefaultRoute" + "NTP" + "IPForward" + "IPMasquerade" + "IPv6PrivacyExtensions" + "IPv6AcceptRA" + "IPv6DuplicateAddressDetection" + "IPv6HopLimit" + "IPv4ProxyARP" + "IPv6ProxyNDP" + "IPv6ProxyNDPAddress" + "IPv6SendRA" + "DHCPPrefixDelegation" + "IPv6MTUBytes" + "Bridge" + "Bond" + "VRF" + "VLAN" + "IPVLAN" + "IPVTAP" + "MACVLAN" + "MACVTAP" + "VXLAN" + "Tunnel" + "MACsec" + "ActiveSlave" + "PrimarySlave" + "ConfigureWithoutCarrier" + "IgnoreCarrierLoss" + "Xfrm" + "KeepConfiguration" + "BatmanAdvanced" + ]) + # Note: For DHCP the values both, none, v4, v6 are deprecated + (assertValueOneOf "DHCP" ["yes" "no" "ipv4" "ipv6"]) + (assertValueOneOf "DHCPServer" boolValues) + (assertValueOneOf "LinkLocalAddressing" ["yes" "no" "ipv4" "ipv6" "fallback" "ipv4-fallback"]) + (assertValueOneOf "IPv6LinkLocalAddressGenerationMode" ["eui64" "none" "stable-privacy" "random"]) + (assertValueOneOf "IPv4LLRoute" boolValues) + (assertValueOneOf "DefaultRouteOnDevice" boolValues) + (assertValueOneOf "LLMNR" (boolValues ++ ["resolve"])) + (assertValueOneOf "MulticastDNS" (boolValues ++ ["resolve"])) + (assertValueOneOf "DNSOverTLS" (boolValues ++ ["opportunistic"])) + (assertValueOneOf "DNSSEC" (boolValues ++ ["allow-downgrade"])) + (assertValueOneOf "LLDP" (boolValues ++ ["routers-only"])) + (assertValueOneOf "EmitLLDP" (boolValues ++ ["nearest-bridge" "non-tpmr-bridge" "customer-bridge"])) + (assertValueOneOf "DNSDefaultRoute" boolValues) + (assertValueOneOf "IPForward" (boolValues ++ ["ipv4" "ipv6"])) + (assertValueOneOf "IPMasquerade" (boolValues ++ ["ipv4" "ipv6" "both"])) + (assertValueOneOf "IPv6PrivacyExtensions" (boolValues ++ ["prefer-public" "kernel"])) + (assertValueOneOf "IPv6AcceptRA" boolValues) + (assertInt "IPv6DuplicateAddressDetection") + (assertMinimum "IPv6DuplicateAddressDetection" 0) + (assertInt "IPv6HopLimit") + (assertMinimum "IPv6HopLimit" 0) + (assertValueOneOf "IPv4ProxyARP" boolValues) + (assertValueOneOf "IPv6ProxyNDP" boolValues) + (assertValueOneOf "IPv6SendRA" boolValues) + (assertValueOneOf "DHCPPrefixDelegation" boolValues) + (assertByteFormat "IPv6MTUBytes") + (assertValueOneOf "ActiveSlave" boolValues) + (assertValueOneOf "PrimarySlave" boolValues) + (assertValueOneOf "ConfigureWithoutCarrier" boolValues) + (assertValueOneOf "KeepConfiguration" (boolValues ++ ["static" "dhcp-on-stop" "dhcp"])) + ]; + + sectionAddress = checkUnitConfig "Address" [ + (assertOnlyFields [ + "Address" + "Peer" + "Broadcast" + "Label" + "PreferredLifetime" + "Scope" + "RouteMetric" + "HomeAddress" + "DuplicateAddressDetection" + "ManageTemporaryAddress" + "AddPrefixRoute" + "AutoJoin" + ]) + (assertHasField "Address") + (assertValueOneOf "PreferredLifetime" ["forever" "infinity" "0" 0]) + (assertInt "RouteMetric") + (assertValueOneOf "HomeAddress" boolValues) + (assertValueOneOf "DuplicateAddressDetection" ["ipv4" "ipv6" "both" "none"]) + (assertValueOneOf "ManageTemporaryAddress" boolValues) + (assertValueOneOf "AddPrefixRoute" boolValues) + (assertValueOneOf "AutoJoin" boolValues) + ]; + + sectionRoutingPolicyRule = checkUnitConfig "RoutingPolicyRule" [ + (assertOnlyFields [ + "TypeOfService" + "From" + "To" + "FirewallMark" + "Table" + "Priority" + "IncomingInterface" + "OutgoingInterface" + "SourcePort" + "DestinationPort" + "IPProtocol" + "InvertRule" + "Family" + "User" + "SuppressPrefixLength" + "Type" + "SuppressInterfaceGroup" + ]) + (assertInt "TypeOfService") + (assertRange "TypeOfService" 0 255) + (assertInt "FirewallMark") + (assertRange "FirewallMark" 1 4294967295) + (assertInt "Priority") + (assertPort "SourcePort") + (assertPort "DestinationPort") + (assertValueOneOf "InvertRule" boolValues) + (assertValueOneOf "Family" ["ipv4" "ipv6" "both"]) + (assertInt "SuppressPrefixLength") + (assertRange "SuppressPrefixLength" 0 128) + (assertValueOneOf "Type" ["blackhole" "unreachable" "prohibit"]) + (assertRange "SuppressInterfaceGroup" 0 2147483647) + ]; + + sectionRoute = checkUnitConfig "Route" [ + (assertOnlyFields [ + "Gateway" + "GatewayOnLink" + "Destination" + "Source" + "Metric" + "IPv6Preference" + "Scope" + "PreferredSource" + "Table" + "Protocol" + "Type" + "InitialCongestionWindow" + "InitialAdvertisedReceiveWindow" + "QuickAck" + "FastOpenNoCookie" + "TTLPropagate" + "MTUBytes" + "IPServiceType" + "MultiPathRoute" + ]) + (assertValueOneOf "GatewayOnLink" boolValues) + (assertInt "Metric") + (assertValueOneOf "IPv6Preference" ["low" "medium" "high"]) + (assertValueOneOf "Scope" ["global" "site" "link" "host" "nowhere"]) + (assertValueOneOf "Type" [ + "unicast" + "local" + "broadcast" + "anycast" + "multicast" + "blackhole" + "unreachable" + "prohibit" + "throw" + "nat" + "xresolve" + ]) + (assertValueOneOf "QuickAck" boolValues) + (assertValueOneOf "FastOpenNoCookie" boolValues) + (assertValueOneOf "TTLPropagate" boolValues) + (assertByteFormat "MTUBytes") + (assertValueOneOf "IPServiceType" ["CS6" "CS4"]) + ]; + + sectionDHCPv4 = checkUnitConfig "DHCPv4" [ + (assertOnlyFields [ + "UseDNS" + "RoutesToDNS" + "UseNTP" + "UseSIP" + "UseMTU" + "Anonymize" + "SendHostname" + "UseHostname" + "Hostname" + "UseDomains" + "UseRoutes" + "UseTimezone" + "ClientIdentifier" + "VendorClassIdentifier" + "UserClass" + "MaxAttempts" + "DUIDType" + "DUIDRawData" + "IAID" + "RequestBroadcast" + "RouteMetric" + "RouteTable" + "RouteMTUBytes" + "ListenPort" + "SendRelease" + "SendDecline" + "BlackList" + "RequestOptions" + "SendOption" + "FallbackLeaseLifetimeSec" + "Label" + "Use6RD" + ]) + (assertValueOneOf "UseDNS" boolValues) + (assertValueOneOf "RoutesToDNS" boolValues) + (assertValueOneOf "UseNTP" boolValues) + (assertValueOneOf "UseSIP" boolValues) + (assertValueOneOf "UseMTU" boolValues) + (assertValueOneOf "Anonymize" boolValues) + (assertValueOneOf "SendHostname" boolValues) + (assertValueOneOf "UseHostname" boolValues) + (assertValueOneOf "UseDomains" (boolValues ++ ["route"])) + (assertValueOneOf "UseRoutes" boolValues) + (assertValueOneOf "UseTimezone" boolValues) + (assertValueOneOf "ClientIdentifier" ["mac" "duid" "duid-only"]) + (assertInt "IAID") + (assertValueOneOf "RequestBroadcast" boolValues) + (assertInt "RouteMetric") + (assertInt "RouteTable") + (assertRange "RouteTable" 0 4294967295) + (assertByteFormat "RouteMTUBytes") + (assertPort "ListenPort") + (assertValueOneOf "SendRelease" boolValues) + (assertValueOneOf "SendDecline" boolValues) + (assertValueOneOf "FallbackLeaseLifetimeSec" ["forever" "infinity"]) + (assertValueOneOf "Use6RD" boolValues) + ]; + + sectionDHCPv6 = checkUnitConfig "DHCPv6" [ + (assertOnlyFields [ + "UseAddress" + "UseDNS" + "UseNTP" + "UseHostname" + "UseDomains" + "RouteMetric" + "RapidCommit" + "MUDURL" + "RequestOptions" + "SendVendorOption" + "PrefixDelegationHint" + "WithoutRA" + "SendOption" + "UserClass" + "VendorClass" + "DUIDType" + "DUIDRawData" + "IAID" + "UseDelegatedPrefix" + "SendRelease" + ]) + (assertValueOneOf "UseAddress" boolValues) + (assertValueOneOf "UseDNS" boolValues) + (assertValueOneOf "UseNTP" boolValues) + (assertValueOneOf "UseHostname" boolValues) + (assertValueOneOf "UseDomains" (boolValues ++ ["route"])) + (assertInt "RouteMetric") + (assertValueOneOf "RapidCommit" boolValues) + (assertValueOneOf "WithoutRA" ["no" "solicit" "information-request"]) + (assertRange "SendOption" 1 65536) + (assertInt "IAID") + (assertValueOneOf "UseDelegatedPrefix" boolValues) + (assertValueOneOf "SendRelease" boolValues) + ]; + + sectionDHCPPrefixDelegation = checkUnitConfig "DHCPPrefixDelegation" [ + (assertOnlyFields [ + "UplinkInterface" + "SubnetId" + "Announce" + "Assign" + "Token" + "ManageTemporaryAddress" + "RouteMetric" + ]) + (assertValueOneOf "Announce" boolValues) + (assertValueOneOf "Assign" boolValues) + (assertValueOneOf "ManageTemporaryAddress" boolValues) + (assertRange "RouteMetric" 0 4294967295) + ]; + + sectionIPv6AcceptRA = checkUnitConfig "IPv6AcceptRA" [ + (assertOnlyFields [ + "UseDNS" + "UseDomains" + "RouteTable" + "UseAutonomousPrefix" + "UseOnLinkPrefix" + "RouterDenyList" + "RouterAllowList" + "PrefixDenyList" + "PrefixAllowList" + "RouteDenyList" + "RouteAllowList" + "DHCPv6Client" + "RouteMetric" + "UseMTU" + "UseGateway" + "UseRoutePrefix" + "Token" + ]) + (assertValueOneOf "UseDNS" boolValues) + (assertValueOneOf "UseDomains" (boolValues ++ ["route"])) + (assertRange "RouteTable" 0 4294967295) + (assertValueOneOf "UseAutonomousPrefix" boolValues) + (assertValueOneOf "UseOnLinkPrefix" boolValues) + (assertValueOneOf "DHCPv6Client" (boolValues ++ ["always"])) + (assertValueOneOf "UseMTU" boolValues) + (assertValueOneOf "UseGateway" boolValues) + (assertValueOneOf "UseRoutePrefix" boolValues) + ]; + + sectionDHCPServer = checkUnitConfig "DHCPServer" [ + (assertOnlyFields [ + "ServerAddress" + "PoolOffset" + "PoolSize" + "DefaultLeaseTimeSec" + "MaxLeaseTimeSec" + "UplinkInterface" + "EmitDNS" + "DNS" + "EmitNTP" + "NTP" + "EmitSIP" + "SIP" + "EmitPOP3" + "POP3" + "EmitSMTP" + "SMTP" + "EmitLPR" + "LPR" + "EmitRouter" + "Router" + "EmitTimezone" + "Timezone" + "SendOption" + "SendVendorOption" + "BindToInterface" + "RelayTarget" + "RelayAgentCircuitId" + "RelayAgentRemoteId" + "BootServerAddress" + "BootServerName" + "BootFilename" + ]) + (assertInt "PoolOffset") + (assertMinimum "PoolOffset" 0) + (assertInt "PoolSize") + (assertMinimum "PoolSize" 0) + (assertValueOneOf "EmitDNS" boolValues) + (assertValueOneOf "EmitNTP" boolValues) + (assertValueOneOf "EmitSIP" boolValues) + (assertValueOneOf "EmitPOP3" boolValues) + (assertValueOneOf "EmitSMTP" boolValues) + (assertValueOneOf "EmitLPR" boolValues) + (assertValueOneOf "EmitRouter" boolValues) + (assertValueOneOf "EmitTimezone" boolValues) + (assertValueOneOf "BindToInterface" boolValues) + ]; + + sectionIPv6SendRA = checkUnitConfig "IPv6SendRA" [ + (assertOnlyFields [ + "Managed" + "OtherInformation" + "RouterLifetimeSec" + "RouterPreference" + "UplinkInterface" + "EmitDNS" + "DNS" + "EmitDomains" + "Domains" + "DNSLifetimeSec" + ]) + (assertValueOneOf "Managed" boolValues) + (assertValueOneOf "OtherInformation" boolValues) + (assertValueOneOf "RouterPreference" ["high" "medium" "low" "normal" "default"]) + (assertValueOneOf "EmitDNS" boolValues) + (assertValueOneOf "EmitDomains" boolValues) + ]; + + sectionIPv6Prefix = checkUnitConfig "IPv6Prefix" [ + (assertOnlyFields [ + "AddressAutoconfiguration" + "OnLink" + "Prefix" + "PreferredLifetimeSec" + "ValidLifetimeSec" + "Assign" + "Token" + ]) + (assertValueOneOf "AddressAutoconfiguration" boolValues) + (assertValueOneOf "OnLink" boolValues) + (assertValueOneOf "Assign" boolValues) + ]; + + sectionIPv6RoutePrefix = checkUnitConfig "IPv6RoutePrefix" [ + (assertOnlyFields [ + "Route" + "LifetimeSec" + ]) + (assertHasField "Route") + (assertInt "LifetimeSec") + ]; + + sectionDHCPServerStaticLease = checkUnitConfig "DHCPServerStaticLease" [ + (assertOnlyFields [ + "MACAddress" + "Address" + ]) + (assertHasField "MACAddress") + (assertHasField "Address") + (assertMacAddress "MACAddress") + ]; + + sectionBridge = checkUnitConfig "Bridge" [ + (assertOnlyFields [ + "UnicastFlood" + "MulticastFlood" + "MulticastToUnicast" + "NeighborSuppression" + "Learning" + "HairPin" + "Isolated" + "UseBPDU" + "FastLeave" + "AllowPortToBeRoot" + "ProxyARP" + "ProxyARPWiFi" + "MulticastRouter" + "Cost" + "Priority" + ]) + (assertValueOneOf "UnicastFlood" boolValues) + (assertValueOneOf "MulticastFlood" boolValues) + (assertValueOneOf "MulticastToUnicast" boolValues) + (assertValueOneOf "NeighborSuppression" boolValues) + (assertValueOneOf "Learning" boolValues) + (assertValueOneOf "HairPin" boolValues) + (assertValueOneOf "Isolated" boolValues) + (assertValueOneOf "UseBPDU" boolValues) + (assertValueOneOf "FastLeave" boolValues) + (assertValueOneOf "AllowPortToBeRoot" boolValues) + (assertValueOneOf "ProxyARP" boolValues) + (assertValueOneOf "ProxyARPWiFi" boolValues) + (assertValueOneOf "MulticastRouter" [ "no" "query" "permanent" "temporary" ]) + (assertInt "Cost") + (assertRange "Cost" 1 65535) + (assertInt "Priority") + (assertRange "Priority" 0 63) + ]; + + sectionBridgeFDB = checkUnitConfig "BridgeFDB" [ + (assertOnlyFields [ + "MACAddress" + "Destination" + "VLANId" + "VNI" + "AssociatedWith" + "OutgoingInterface" + ]) + (assertHasField "MACAddress") + (assertInt "VLANId") + (assertRange "VLANId" 0 4094) + (assertInt "VNI") + (assertRange "VNI" 1 16777215) + (assertValueOneOf "AssociatedWith" [ "use" "self" "master" "router" ]) + ]; + + sectionBridgeMDB = checkUnitConfig "BridgeMDB" [ + (assertOnlyFields [ + "MulticastGroupAddress" + "VLANId" + ]) + (assertHasField "MulticastGroupAddress") + (assertInt "VLANId") + (assertRange "VLANId" 0 4094) + ]; + + sectionLLDP = checkUnitConfig "LLDP" [ + (assertOnlyFields [ + "MUDURL" + ]) + ]; + + sectionCAN = checkUnitConfig "CAN" [ + (assertOnlyFields [ + "BitRate" + "SamplePoint" + "TimeQuantaNSec" + "PropagationSegment" + "PhaseBufferSegment1" + "PhaseBufferSegment2" + "SyncJumpWidth" + "DataBitRate" + "DataSamplePoint" + "DataTimeQuantaNSec" + "DataPropagationSegment" + "DataPhaseBufferSegment1" + "DataPhaseBufferSegment2" + "DataSyncJumpWidth" + "FDMode" + "FDNonISO" + "RestartSec" + "Termination" + "TripleSampling" + "BusErrorReporting" + "ListenOnly" + "Loopback" + "OneShot" + "PresumeAck" + "ClassicDataLengthCode" + ]) + (assertInt "TimeQuantaNSec" ) + (assertRange "TimeQuantaNSec" 0 4294967295 ) + (assertInt "PropagationSegment" ) + (assertRange "PropagationSegment" 0 4294967295 ) + (assertInt "PhaseBufferSegment1" ) + (assertRange "PhaseBufferSegment1" 0 4294967295 ) + (assertInt "PhaseBufferSegment2" ) + (assertRange "PhaseBufferSegment2" 0 4294967295 ) + (assertInt "SyncJumpWidth" ) + (assertRange "SyncJumpWidth" 0 4294967295 ) + (assertInt "DataTimeQuantaNSec" ) + (assertRange "DataTimeQuantaNSec" 0 4294967295 ) + (assertInt "DataPropagationSegment" ) + (assertRange "DataPropagationSegment" 0 4294967295 ) + (assertInt "DataPhaseBufferSegment1" ) + (assertRange "DataPhaseBufferSegment1" 0 4294967295 ) + (assertInt "DataPhaseBufferSegment2" ) + (assertRange "DataPhaseBufferSegment2" 0 4294967295 ) + (assertInt "DataSyncJumpWidth" ) + (assertRange "DataSyncJumpWidth" 0 4294967295 ) + (assertValueOneOf "FDMode" boolValues) + (assertValueOneOf "FDNonISO" boolValues) + (assertValueOneOf "TripleSampling" boolValues) + (assertValueOneOf "BusErrorReporting" boolValues) + (assertValueOneOf "ListenOnly" boolValues) + (assertValueOneOf "Loopback" boolValues) + (assertValueOneOf "OneShot" boolValues) + (assertValueOneOf "PresumeAck" boolValues) + (assertValueOneOf "ClassicDataLengthCode" boolValues) + ]; + + sectionIPoIB = checkUnitConfig "IPoIB" [ + (assertOnlyFields [ + "Mode" + "IgnoreUserspaceMulticastGroup" + ]) + (assertValueOneOf "Mode" [ "datagram" "connected" ]) + (assertValueOneOf "IgnoreUserspaceMulticastGroup" boolValues) + ]; + + sectionQDisc = checkUnitConfig "QDisc" [ + (assertOnlyFields [ + "Parent" + "Handle" + ]) + (assertValueOneOf "Parent" [ "clsact" "ingress" ]) + ]; + + sectionNetworkEmulator = checkUnitConfig "NetworkEmulator" [ + (assertOnlyFields [ + "Parent" + "Handle" + "DelaySec" + "DelayJitterSec" + "PacketLimit" + "LossRate" + "DuplicateRate" + ]) + (assertInt "PacketLimit") + (assertRange "PacketLimit" 0 4294967294) + ]; + + sectionTokenBucketFilter = checkUnitConfig "TokenBucketFilter" [ + (assertOnlyFields [ + "Parent" + "Handle" + "LatencySec" + "LimitBytes" + "BurstBytes" + "Rate" + "MPUBytes" + "PeakRate" + "MTUBytes" + ]) + ]; + + sectionPIE = checkUnitConfig "PIE" [ + (assertOnlyFields [ + "Parent" + "Handle" + "PacketLimit" + ]) + (assertInt "PacketLimit") + (assertRange "PacketLimit" 1 4294967294) + ]; + + sectionFlowQueuePIE = checkUnitConfig "FlowQueuePIE" [ + (assertOnlyFields [ + "Parent" + "Handle" + "PacketLimit" + ]) + (assertInt "PacketLimit") + (assertRange "PacketLimit" 1 4294967294) + ]; + + sectionStochasticFairBlue = checkUnitConfig "StochasticFairBlue" [ + (assertOnlyFields [ + "Parent" + "Handle" + "PacketLimit" + ]) + (assertInt "PacketLimit") + (assertRange "PacketLimit" 1 4294967294) + ]; + + sectionStochasticFairnessQueueing = checkUnitConfig "StochasticFairnessQueueing" [ + (assertOnlyFields [ + "Parent" + "Handle" + "PerturbPeriodSec" + ]) + (assertInt "PerturbPeriodSec") + ]; + + sectionBFIFO = checkUnitConfig "BFIFO" [ + (assertOnlyFields [ + "Parent" + "Handle" + "LimitBytes" + ]) + ]; + + sectionPFIFO = checkUnitConfig "PFIFO" [ + (assertOnlyFields [ + "Parent" + "Handle" + "PacketLimit" + ]) + (assertInt "PacketLimit") + (assertRange "PacketLimit" 0 4294967294) + ]; + + sectionPFIFOHeadDrop = checkUnitConfig "PFIFOHeadDrop" [ + (assertOnlyFields [ + "Parent" + "Handle" + "PacketLimit" + ]) + (assertInt "PacketLimit") + (assertRange "PacketLimit" 0 4294967294) + ]; + + sectionPFIFOFast = checkUnitConfig "PFIFOFast" [ + (assertOnlyFields [ + "Parent" + "Handle" + ]) + ]; + + sectionCAKE = checkUnitConfig "CAKE" [ + (assertOnlyFields [ + "Parent" + "Handle" + "Bandwidth" + "AutoRateIngress" + "OverheadBytes" + "MPUBytes" + "CompensationMode" + "UseRawPacketSize" + "FlowIsolationMode" + "NAT" + "PriorityQueueingPreset" + "FirewallMark" + "Wash" + "SplitGSO" + "AckFilter" + ]) + (assertValueOneOf "AutoRateIngress" boolValues) + (assertInt "OverheadBytes") + (assertRange "OverheadBytes" (-64) 256) + (assertInt "MPUBytes") + (assertRange "MPUBytes" 1 256) + (assertValueOneOf "CompensationMode" [ "none" "atm" "ptm" ]) + (assertValueOneOf "UseRawPacketSize" boolValues) + (assertValueOneOf "FlowIsolationMode" + [ + "none" + "src-host" + "dst-host" + "hosts" + "flows" + "dual-src-host" + "dual-dst-host" + "triple" + ]) + (assertValueOneOf "NAT" boolValues) + (assertValueOneOf "PriorityQueueingPreset" + [ + "besteffort" + "precedence" + "diffserv8" + "diffserv4" + "diffserv3" + ]) + (assertInt "FirewallMark") + (assertRange "FirewallMark" 1 4294967295) + (assertValueOneOf "Wash" boolValues) + (assertValueOneOf "SplitGSO" boolValues) + (assertValueOneOf "AckFilter" (boolValues ++ ["aggressive"])) + ]; + + sectionControlledDelay = checkUnitConfig "ControlledDelay" [ + (assertOnlyFields [ + "Parent" + "Handle" + "PacketLimit" + "TargetSec" + "IntervalSec" + "ECN" + "CEThresholdSec" + ]) + (assertValueOneOf "ECN" boolValues) + ]; + + sectionDeficitRoundRobinScheduler = checkUnitConfig "DeficitRoundRobinScheduler" [ + (assertOnlyFields [ + "Parent" + "Handle" + ]) + ]; + + sectionDeficitRoundRobinSchedulerClass = checkUnitConfig "DeficitRoundRobinSchedulerClass" [ + (assertOnlyFields [ + "Parent" + "Handle" + "QuantumBytes" + ]) + ]; + + sectionEnhancedTransmissionSelection = checkUnitConfig "EnhancedTransmissionSelection" [ + (assertOnlyFields [ + "Parent" + "Handle" + "Bands" + "StrictBands" + "QuantumBytes" + "PriorityMap" + ]) + (assertInt "Bands") + (assertRange "Bands" 1 16) + (assertInt "StrictBands") + (assertRange "StrictBands" 1 16) + ]; + + sectionGenericRandomEarlyDetection = checkUnitConfig "GenericRandomEarlyDetection" [ + (assertOnlyFields [ + "Parent" + "Handle" + "VirtualQueues" + "DefaultVirtualQueue" + "GenericRIO" + ]) + (assertInt "VirtualQueues") + (assertRange "VirtualQueues" 1 16) + (assertInt "DefaultVirtualQueue") + (assertRange "DefaultVirtualQueue" 1 16) + (assertValueOneOf "GenericRIO" boolValues) + ]; + + sectionFairQueueingControlledDelay = checkUnitConfig "FairQueueingControlledDelay" [ + (assertOnlyFields [ + "Parent" + "Handle" + "PacketLimit" + "MemoryLimitBytes" + "Flows" + "TargetSec" + "IntervalSec" + "QuantumBytes" + "ECN" + "CEThresholdSec" + ]) + (assertInt "PacketLimit") + (assertInt "Flows") + (assertValueOneOf "ECN" boolValues) + ]; + + sectionFairQueueing = checkUnitConfig "FairQueueing" [ + (assertOnlyFields [ + "Parent" + "Handle" + "PacketLimit" + "FlowLimit" + "QuantumBytes" + "InitualQuantumBytes" + "MaximumRate" + "Buckets" + "OrphanMask" + "Pacing" + "CEThresholdSec" + ]) + (assertInt "PacketLimit") + (assertInt "FlowLimit") + (assertInt "OrphanMask") + (assertValueOneOf "Pacing" boolValues) + ]; + + sectionTrivialLinkEqualizer = checkUnitConfig "TrivialLinkEqualizer" [ + (assertOnlyFields [ + "Parent" + "Handle" + "Id" + ]) + ]; + + sectionHierarchyTokenBucket = checkUnitConfig "HierarchyTokenBucket" [ + (assertOnlyFields [ + "Parent" + "Handle" + "DefaultClass" + "RateToQuantum" + ]) + (assertInt "RateToQuantum") + ]; + + sectionHierarchyTokenBucketClass = checkUnitConfig "HierarchyTokenBucketClass" [ + (assertOnlyFields [ + "Parent" + "ClassId" + "Priority" + "QuantumBytes" + "MTUBytes" + "OverheadBytes" + "Rate" + "CeilRate" + "BufferBytes" + "CeilBufferBytes" + ]) + ]; + + sectionHeavyHitterFilter = checkUnitConfig "HeavyHitterFilter" [ + (assertOnlyFields [ + "Parent" + "Handle" + "PacketLimit" + ]) + (assertInt "PacketLimit") + (assertRange "PacketLimit" 0 4294967294) + ]; + + sectionQuickFairQueueing = checkUnitConfig "QuickFairQueueing" [ + (assertOnlyFields [ + "Parent" + "Handle" + ]) + ]; + + sectionQuickFairQueueingClass = checkUnitConfig "QuickFairQueueingClass" [ + (assertOnlyFields [ + "Parent" + "ClassId" + "Weight" + "MaxPacketBytes" + ]) + (assertInt "Weight") + (assertRange "Weight" 1 1023) + ]; + + sectionBridgeVLAN = checkUnitConfig "BridgeVLAN" [ + (assertOnlyFields [ + "VLAN" + "EgressUntagged" + "PVID" + ]) + (assertInt "PVID") + (assertRange "PVID" 0 4094) + ]; + }; + }; + + commonNetworkOptions = { + + enable = mkOption { + default = true; + type = types.bool; + description = lib.mdDoc '' + Whether to manage network configuration using {command}`systemd-network`. + + This also enables {option}`systemd.networkd.enable`. + ''; + }; + + matchConfig = mkOption { + default = {}; + example = { Name = "eth0"; }; + type = types.attrsOf unitOption; + description = lib.mdDoc '' + Each attribute in this set specifies an option in the + `[Match]` section of the unit. See + {manpage}`systemd.link(5)` + {manpage}`systemd.netdev(5)` + {manpage}`systemd.network(5)` + for details. + ''; + }; + + extraConfig = mkOption { + default = ""; + type = types.lines; + description = lib.mdDoc "Extra configuration append to unit"; + }; + }; + + networkdOptions = { + networkConfig = mkOption { + default = {}; + example = { SpeedMeter = true; ManageForeignRoutingPolicyRules = false; }; + type = types.addCheck (types.attrsOf unitOption) check.global.sectionNetwork; + description = lib.mdDoc '' + Each attribute in this set specifies an option in the + `[Network]` section of the networkd config. + See {manpage}`networkd.conf(5)` for details. + ''; + }; + + dhcpV4Config = mkOption { + default = {}; + example = { DUIDType = "vendor"; }; + type = types.addCheck (types.attrsOf unitOption) check.global.sectionDHCPv4; + description = lib.mdDoc '' + Each attribute in this set specifies an option in the + `[DHCPv4]` section of the networkd config. + See {manpage}`networkd.conf(5)` for details. + ''; + }; + + dhcpV6Config = mkOption { + default = {}; + example = { DUIDType = "vendor"; }; + type = types.addCheck (types.attrsOf unitOption) check.global.sectionDHCPv6; + description = lib.mdDoc '' + Each attribute in this set specifies an option in the + `[DHCPv6]` section of the networkd config. + See {manpage}`networkd.conf(5)` for details. + ''; + }; + }; + + linkOptions = commonNetworkOptions // { + # overwrite enable option from above + enable = mkOption { + default = true; + type = types.bool; + description = lib.mdDoc '' + Whether to enable this .link unit. It's handled by udev no matter if {command}`systemd-networkd` is enabled or not + ''; + }; + + linkConfig = mkOption { + default = {}; + example = { MACAddress = "00:ff:ee:aa:cc:dd"; }; + type = types.addCheck (types.attrsOf unitOption) check.link.sectionLink; + description = lib.mdDoc '' + Each attribute in this set specifies an option in the + `[Link]` section of the unit. See + {manpage}`systemd.link(5)` for details. + ''; + }; + + }; + + + l2tpSessionOptions = { + options = { + l2tpSessionConfig = mkOption { + default = {}; + type = types.addCheck (types.attrsOf unitOption) check.netdev.sectionL2TPSession; + description = lib.mdDoc '' + Each attribute in this set specifies an option in the + `[L2TPSession]` section of the unit. See + {manpage}`systemd.netdev(5)` for details. + ''; + }; + }; + }; + + wireguardPeerOptions = { + options = { + wireguardPeerConfig = mkOption { + default = {}; + type = types.addCheck (types.attrsOf unitOption) check.netdev.sectionWireGuardPeer; + description = lib.mdDoc '' + Each attribute in this set specifies an option in the + `[WireGuardPeer]` section of the unit. See + {manpage}`systemd.netdev(5)` for details. + ''; + }; + }; + }; + + netdevOptions = commonNetworkOptions // { + + netdevConfig = mkOption { + example = { Name = "mybridge"; Kind = "bridge"; }; + type = types.addCheck (types.attrsOf unitOption) check.netdev.sectionNetdev; + description = lib.mdDoc '' + Each attribute in this set specifies an option in the + `[Netdev]` section of the unit. See + {manpage}`systemd.netdev(5)` for details. + ''; + }; + + vlanConfig = mkOption { + default = {}; + example = { Id = 4; }; + type = types.addCheck (types.attrsOf unitOption) check.netdev.sectionVLAN; + description = lib.mdDoc '' + Each attribute in this set specifies an option in the + `[VLAN]` section of the unit. See + {manpage}`systemd.netdev(5)` for details. + ''; + }; + + ipvlanConfig = mkOption { + default = {}; + example = { Mode = "L2"; Flags = "private"; }; + type = types.addCheck (types.attrsOf unitOption) check.netdev.sectionIPVLAN; + description = lib.mdDoc '' + Each attribute in this set specifies an option in the `[IPVLAN]` section of the unit. + See {manpage}`systemd.netdev(5)` for details. + ''; + }; + + ipvtapConfig = mkOption { + default = {}; + example = { Mode = "L3"; Flags = "vepa"; }; + type = types.addCheck (types.attrsOf unitOption) check.netdev.sectionIPVTAP; + description = lib.mdDoc '' + Each attribute in this set specifies an option in the `[IPVTAP]` section of the unit. + See {manpage}`systemd.netdev(5)` for details. + ''; + }; + + macvlanConfig = mkOption { + default = {}; + example = { Mode = "private"; }; + type = types.addCheck (types.attrsOf unitOption) check.netdev.sectionMACVLAN; + description = lib.mdDoc '' + Each attribute in this set specifies an option in the + `[MACVLAN]` section of the unit. See + {manpage}`systemd.netdev(5)` for details. + ''; + }; + + vxlanConfig = mkOption { + default = {}; + type = types.addCheck (types.attrsOf unitOption) check.netdev.sectionVXLAN; + description = lib.mdDoc '' + Each attribute in this set specifies an option in the + `[VXLAN]` section of the unit. See + {manpage}`systemd.netdev(5)` for details. + ''; + }; + + tunnelConfig = mkOption { + default = {}; + example = { Remote = "192.168.1.1"; }; + type = types.addCheck (types.attrsOf unitOption) check.netdev.sectionTunnel; + description = lib.mdDoc '' + Each attribute in this set specifies an option in the + `[Tunnel]` section of the unit. See + {manpage}`systemd.netdev(5)` for details. + ''; + }; + + fooOverUDPConfig = mkOption { + default = { }; + example = { Port = 9001; }; + type = types.addCheck (types.attrsOf unitOption) check.netdev.sectionFooOverUDP; + description = lib.mdDoc '' + Each attribute in this set specifies an option in the + `[FooOverUDP]` section of the unit. See + {manpage}`systemd.netdev(5)` for details. + ''; + }; + + peerConfig = mkOption { + default = {}; + example = { Name = "veth2"; }; + type = types.addCheck (types.attrsOf unitOption) check.netdev.sectionPeer; + description = lib.mdDoc '' + Each attribute in this set specifies an option in the + `[Peer]` section of the unit. See + {manpage}`systemd.netdev(5)` for details. + ''; + }; + + tunConfig = mkOption { + default = {}; + example = { User = "openvpn"; }; + type = types.addCheck (types.attrsOf unitOption) check.netdev.sectionTun; + description = lib.mdDoc '' + Each attribute in this set specifies an option in the + `[Tun]` section of the unit. See + {manpage}`systemd.netdev(5)` for details. + ''; + }; + + tapConfig = mkOption { + default = {}; + example = { User = "openvpn"; }; + type = types.addCheck (types.attrsOf unitOption) check.netdev.sectionTap; + description = lib.mdDoc '' + Each attribute in this set specifies an option in the + `[Tap]` section of the unit. See + {manpage}`systemd.netdev(5)` for details. + ''; + }; + + l2tpConfig = mkOption { + default = {}; + example = { + TunnelId = 10; + PeerTunnelId = 12; + Local = "static"; + Remote = "192.168.30.101"; + EncapsulationType = "ip"; + }; + type = types.addCheck (types.attrsOf unitOption) check.netdev.sectionL2TP; + description = lib.mdDoc '' + Each attribute in this set specifies an option in the + `[L2TP]` section of the unit. See + {manpage}`systemd.netdev(5)` for details. + ''; + }; + + l2tpSessions = mkOption { + default = []; + example = [ { l2tpSessionConfig={ + SessionId = 25; + PeerSessionId = 26; + Name = "l2tp-sess"; + };}]; + type = with types; listOf (submodule l2tpSessionOptions); + description = lib.mdDoc '' + Each item in this array specifies an option in the + `[L2TPSession]` section of the unit. See + {manpage}`systemd.netdev(5)` for details. + ''; + }; + + wireguardConfig = mkOption { + default = {}; + example = { + PrivateKeyFile = "/etc/wireguard/secret.key"; + ListenPort = 51820; + FirewallMark = 42; + }; + type = types.addCheck (types.attrsOf unitOption) check.netdev.sectionWireGuard; + description = lib.mdDoc '' + Each attribute in this set specifies an option in the + `[WireGuard]` section of the unit. See + {manpage}`systemd.netdev(5)` for details. + Use `PrivateKeyFile` instead of + `PrivateKey`: the nix store is + world-readable. + ''; + }; + + wireguardPeers = mkOption { + default = []; + example = [ { wireguardPeerConfig={ + Endpoint = "192.168.1.1:51820"; + PublicKey = "27s0OvaBBdHoJYkH9osZpjpgSOVNw+RaKfboT/Sfq0g="; + PresharedKeyFile = "/etc/wireguard/psk.key"; + AllowedIPs = [ "10.0.0.1/32" ]; + PersistentKeepalive = 15; + };}]; + type = with types; listOf (submodule wireguardPeerOptions); + description = lib.mdDoc '' + Each item in this array specifies an option in the + `[WireGuardPeer]` section of the unit. See + {manpage}`systemd.netdev(5)` for details. + Use `PresharedKeyFile` instead of + `PresharedKey`: the nix store is + world-readable. + ''; + }; + + bondConfig = mkOption { + default = {}; + example = { Mode = "802.3ad"; }; + type = types.addCheck (types.attrsOf unitOption) check.netdev.sectionBond; + description = lib.mdDoc '' + Each attribute in this set specifies an option in the + `[Bond]` section of the unit. See + {manpage}`systemd.netdev(5)` for details. + ''; + }; + + xfrmConfig = mkOption { + default = {}; + example = { InterfaceId = 1; }; + type = types.addCheck (types.attrsOf unitOption) check.netdev.sectionXfrm; + description = lib.mdDoc '' + Each attribute in this set specifies an option in the + `[Xfrm]` section of the unit. See + {manpage}`systemd.netdev(5)` for details. + ''; + }; + + vrfConfig = mkOption { + default = {}; + example = { Table = 2342; }; + type = types.addCheck (types.attrsOf unitOption) check.netdev.sectionVRF; + description = lib.mdDoc '' + Each attribute in this set specifies an option in the + `[VRF]` section of the unit. See + {manpage}`systemd.netdev(5)` for details. + A detailed explanation about how VRFs work can be found in the + [kernel docs](https://www.kernel.org/doc/Documentation/networking/vrf.txt). + ''; + }; + + wlanConfig = mkOption { + default = {}; + example = { PhysicalDevice = 0; Type = "station"; }; + type = types.addCheck (types.attrsOf unitOption) check.netdev.sectionWLAN; + description = lib.mdDoc '' + Each attribute in this set specifies an option in the `[WLAN]` section of the unit. + See {manpage}`systemd.netdev(5)` for details. + ''; + }; + + batmanAdvancedConfig = mkOption { + default = {}; + example = { + GatewayMode = "server"; + RoutingAlgorithm = "batman-v"; + }; + type = types.addCheck (types.attrsOf unitOption) check.netdev.sectionBatmanAdvanced; + description = lib.mdDoc '' + Each attribute in this set specifies an option in the + `[BatmanAdvanced]` section of the unit. See + {manpage}`systemd.netdev(5)` for details. + ''; + }; + + }; + + addressOptions = { + options = { + addressConfig = mkOption { + example = { Address = "192.168.0.100/24"; }; + type = types.addCheck (types.attrsOf unitOption) check.network.sectionAddress; + description = lib.mdDoc '' + Each attribute in this set specifies an option in the + `[Address]` section of the unit. See + {manpage}`systemd.network(5)` for details. + ''; + }; + }; + }; + + routingPolicyRulesOptions = { + options = { + routingPolicyRuleConfig = mkOption { + default = { }; + example = { Table = 10; IncomingInterface = "eth1"; Family = "both"; }; + type = types.addCheck (types.attrsOf unitOption) check.network.sectionRoutingPolicyRule; + description = lib.mdDoc '' + Each attribute in this set specifies an option in the + `[RoutingPolicyRule]` section of the unit. See + {manpage}`systemd.network(5)` for details. + ''; + }; + }; + }; + + routeOptions = { + options = { + routeConfig = mkOption { + default = {}; + example = { Gateway = "192.168.0.1"; }; + type = types.addCheck (types.attrsOf unitOption) check.network.sectionRoute; + description = lib.mdDoc '' + Each attribute in this set specifies an option in the + `[Route]` section of the unit. See + {manpage}`systemd.network(5)` for details. + ''; + }; + }; + }; + + ipv6PrefixOptions = { + options = { + ipv6PrefixConfig = mkOption { + default = {}; + example = { Prefix = "fd00::/64"; }; + type = types.addCheck (types.attrsOf unitOption) check.network.sectionIPv6Prefix; + description = lib.mdDoc '' + Each attribute in this set specifies an option in the + `[IPv6Prefix]` section of the unit. See + {manpage}`systemd.network(5)` for details. + ''; + }; + }; + }; + + ipv6RoutePrefixOptions = { + options = { + ipv6RoutePrefixConfig = mkOption { + default = {}; + example = { Route = "fd00::/64"; }; + type = types.addCheck (types.attrsOf unitOption) check.network.sectionIPv6RoutePrefix; + description = lib.mdDoc '' + Each attribute in this set specifies an option in the + `[IPv6RoutePrefix]` section of the unit. See + {manpage}`systemd.network(5)` for details. + ''; + }; + }; + }; + + dhcpServerStaticLeaseOptions = { + options = { + dhcpServerStaticLeaseConfig = mkOption { + default = {}; + example = { MACAddress = "65:43:4a:5b:d8:5f"; Address = "192.168.1.42"; }; + type = types.addCheck (types.attrsOf unitOption) check.network.sectionDHCPServerStaticLease; + description = lib.mdDoc '' + Each attribute in this set specifies an option in the + `[DHCPServerStaticLease]` section of the unit. See + {manpage}`systemd.network(5)` for details. + + Make sure to configure the corresponding client interface to use + `ClientIdentifier=mac`. + ''; + }; + }; + }; + + bridgeFDBOptions = { + options = { + bridgeFDBConfig = mkOption { + default = {}; + example = { MACAddress = "65:43:4a:5b:d8:5f"; Destination = "192.168.1.42"; VNI = 20; }; + type = types.addCheck (types.attrsOf unitOption) check.network.sectionBridgeFDB; + description = lib.mdDoc '' + Each attribute in this set specifies an option in the + `[BridgeFDB]` section of the unit. See + {manpage}`systemd.network(5)` for details. + ''; + }; + }; + }; + + bridgeMDBOptions = { + options = { + bridgeMDBConfig = mkOption { + default = {}; + example = { MulticastGroupAddress = "ff02::1:2:3:4"; VLANId = 10; }; + type = types.addCheck (types.attrsOf unitOption) check.network.sectionBridgeMDB; + description = lib.mdDoc '' + Each attribute in this set specifies an option in the + `[BridgeMDB]` section of the unit. See + {manpage}`systemd.network(5)` for details. + ''; + }; + }; + }; + + bridgeVLANOptions = { + options = { + bridgeVLANConfig = mkOption { + default = {}; + example = { VLAN = 20; }; + type = types.addCheck (types.attrsOf unitOption) check.network.sectionBridgeVLAN; + description = lib.mdDoc '' + Each attribute in this set specifies an option in the + `[BridgeVLAN]` section of the unit. See + {manpage}`systemd.network(5)` for details. + ''; + }; + }; + }; + + networkOptions = commonNetworkOptions // { + + linkConfig = mkOption { + default = {}; + example = { Unmanaged = true; }; + type = types.addCheck (types.attrsOf unitOption) check.network.sectionLink; + description = lib.mdDoc '' + Each attribute in this set specifies an option in the + `[Link]` section of the unit. See + {manpage}`systemd.network(5)` for details. + ''; + }; + + networkConfig = mkOption { + default = {}; + example = { Description = "My Network"; }; + type = types.addCheck (types.attrsOf unitOption) check.network.sectionNetwork; + description = lib.mdDoc '' + Each attribute in this set specifies an option in the + `[Network]` section of the unit. See + {manpage}`systemd.network(5)` for details. + ''; + }; + + # systemd.network.networks.*.dhcpConfig has been deprecated in favor of ….dhcpV4Config + # Produce a nice warning message so users know it is gone. + dhcpConfig = mkOption { + visible = false; + apply = _: throw "The option `systemd.network.networks.*.dhcpConfig` can no longer be used since it's been removed. Please use `systemd.network.networks.*.dhcpV4Config` instead."; + }; + + dhcpV4Config = mkOption { + default = {}; + example = { UseDNS = true; UseRoutes = true; }; + type = types.addCheck (types.attrsOf unitOption) check.network.sectionDHCPv4; + description = lib.mdDoc '' + Each attribute in this set specifies an option in the + `[DHCPv4]` section of the unit. See + {manpage}`systemd.network(5)` for details. + ''; + }; + + dhcpV6Config = mkOption { + default = {}; + example = { UseDNS = true; }; + type = types.addCheck (types.attrsOf unitOption) check.network.sectionDHCPv6; + description = lib.mdDoc '' + Each attribute in this set specifies an option in the + `[DHCPv6]` section of the unit. See + {manpage}`systemd.network(5)` for details. + ''; + }; + + dhcpV6PrefixDelegationConfig = mkOption { + visible = false; + apply = _: throw "The option `systemd.network.networks.<name>.dhcpV6PrefixDelegationConfig` has been renamed to `systemd.network.networks.<name>.dhcpPrefixDelegationConfig`."; + }; + + dhcpPrefixDelegationConfig = mkOption { + default = {}; + example = { SubnetId = "auto"; Announce = true; }; + type = types.addCheck (types.attrsOf unitOption) check.network.sectionDHCPPrefixDelegation; + description = lib.mdDoc '' + Each attribute in this set specifies an option in the + `[DHCPPrefixDelegation]` section of the unit. See + {manpage}`systemd.network(5)` for details. + ''; + }; + + ipv6AcceptRAConfig = mkOption { + default = {}; + example = { UseDNS = true; DHCPv6Client = "always"; }; + type = types.addCheck (types.attrsOf unitOption) check.network.sectionIPv6AcceptRA; + description = lib.mdDoc '' + Each attribute in this set specifies an option in the + `[IPv6AcceptRA]` section of the unit. See + {manpage}`systemd.network(5)` for details. + ''; + }; + + dhcpServerConfig = mkOption { + default = {}; + example = { PoolOffset = 50; EmitDNS = false; }; + type = types.addCheck (types.attrsOf unitOption) check.network.sectionDHCPServer; + description = lib.mdDoc '' + Each attribute in this set specifies an option in the + `[DHCPServer]` section of the unit. See + {manpage}`systemd.network(5)` for details. + ''; + }; + + # systemd.network.networks.*.ipv6PrefixDelegationConfig has been deprecated + # in 247 in favor of systemd.network.networks.*.ipv6SendRAConfig. + ipv6PrefixDelegationConfig = mkOption { + visible = false; + apply = _: throw "The option `systemd.network.networks.*.ipv6PrefixDelegationConfig` has been replaced by `systemd.network.networks.*.ipv6SendRAConfig`."; + }; + + ipv6SendRAConfig = mkOption { + default = {}; + example = { EmitDNS = true; Managed = true; OtherInformation = true; }; + type = types.addCheck (types.attrsOf unitOption) check.network.sectionIPv6SendRA; + description = lib.mdDoc '' + Each attribute in this set specifies an option in the + `[IPv6SendRA]` section of the unit. See + {manpage}`systemd.network(5)` for details. + ''; + }; + + dhcpServerStaticLeases = mkOption { + default = []; + example = [ { dhcpServerStaticLeaseConfig = { MACAddress = "65:43:4a:5b:d8:5f"; Address = "192.168.1.42"; }; } ]; + type = with types; listOf (submodule dhcpServerStaticLeaseOptions); + description = lib.mdDoc '' + A list of DHCPServerStaticLease sections to be added to the unit. See + {manpage}`systemd.network(5)` for details. + ''; + }; + + ipv6Prefixes = mkOption { + default = []; + example = [ { ipv6PrefixConfig = { AddressAutoconfiguration = true; OnLink = true; }; } ]; + type = with types; listOf (submodule ipv6PrefixOptions); + description = lib.mdDoc '' + A list of ipv6Prefix sections to be added to the unit. See + {manpage}`systemd.network(5)` for details. + ''; + }; + + ipv6RoutePrefixes = mkOption { + default = []; + example = [ { ipv6RoutePrefixConfig = { Route = "fd00::/64"; LifetimeSec = 3600; }; } ]; + type = with types; listOf (submodule ipv6RoutePrefixOptions); + description = lib.mdDoc '' + A list of ipv6RoutePrefix sections to be added to the unit. See + {manpage}`systemd.network(5)` for details. + ''; + }; + + bridgeConfig = mkOption { + default = {}; + example = { MulticastFlood = false; Cost = 20; }; + type = types.addCheck (types.attrsOf unitOption) check.network.sectionBridge; + description = lib.mdDoc '' + Each attribute in this set specifies an option in the + `[Bridge]` section of the unit. See + {manpage}`systemd.network(5)` for details. + ''; + }; + + bridgeFDBs = mkOption { + default = []; + example = [ { bridgeFDBConfig = { MACAddress = "90:e2:ba:43:fc:71"; Destination = "192.168.100.4"; VNI = 3600; }; } ]; + type = with types; listOf (submodule bridgeFDBOptions); + description = lib.mdDoc '' + A list of BridgeFDB sections to be added to the unit. See + {manpage}`systemd.network(5)` for details. + ''; + }; + + bridgeMDBs = mkOption { + default = []; + example = [ { bridgeMDBConfig = { MulticastGroupAddress = "ff02::1:2:3:4"; VLANId = 10; } ; } ]; + type = with types; listOf (submodule bridgeMDBOptions); + description = lib.mdDoc '' + A list of BridgeMDB sections to be added to the unit. See + {manpage}`systemd.network(5)` for details. + ''; + }; + + lldpConfig = mkOption { + default = {}; + example = { MUDURL = "https://things.example.org/product_abc123/v5"; }; + type = types.addCheck (types.attrsOf unitOption) check.network.sectionLLDP; + description = lib.mdDoc '' + Each attribute in this set specifies an option in the + `[LLDP]` section of the unit. See + {manpage}`systemd.network(5)` for details. + ''; + }; + + canConfig = mkOption { + default = {}; + example = { }; + type = types.addCheck (types.attrsOf unitOption) check.network.sectionCAN; + description = lib.mdDoc '' + Each attribute in this set specifies an option in the + `[CAN]` section of the unit. See + {manpage}`systemd.network(5)` for details. + ''; + }; + + ipoIBConfig = mkOption { + default = {}; + example = { }; + type = types.addCheck (types.attrsOf unitOption) check.network.sectionIPoIB; + description = lib.mdDoc '' + Each attribute in this set specifies an option in the + `[IPoIB]` section of the unit. See + {manpage}`systemd.network(5)` for details. + ''; + }; + + qdiscConfig = mkOption { + default = {}; + example = { Parent = "ingress"; }; + type = types.addCheck (types.attrsOf unitOption) check.network.sectionQDisc; + description = lib.mdDoc '' + Each attribute in this set specifies an option in the + `[QDisc]` section of the unit. See + {manpage}`systemd.network(5)` for details. + ''; + }; + + networkEmulatorConfig = mkOption { + default = {}; + example = { Parent = "ingress"; DelaySec = "20msec"; }; + type = types.addCheck (types.attrsOf unitOption) check.network.sectionNetworkEmulator; + description = lib.mdDoc '' + Each attribute in this set specifies an option in the + `[NetworkEmulator]` section of the unit. See + {manpage}`systemd.network(5)` for details. + ''; + }; + + tokenBucketFilterConfig = mkOption { + default = {}; + example = { Parent = "ingress"; Rate = "100k"; }; + type = types.addCheck (types.attrsOf unitOption) check.network.sectionTokenBucketFilter; + description = lib.mdDoc '' + Each attribute in this set specifies an option in the + `[TokenBucketFilter]` section of the unit. See + {manpage}`systemd.network(5)` for details. + ''; + }; + + pieConfig = mkOption { + default = {}; + example = { Parent = "ingress"; PacketLimit = "3847"; }; + type = types.addCheck (types.attrsOf unitOption) check.network.sectionPIE; + description = lib.mdDoc '' + Each attribute in this set specifies an option in the + `[PIE]` section of the unit. See + {manpage}`systemd.network(5)` for details. + ''; + }; + + flowQueuePIEConfig = mkOption { + default = {}; + example = { Parent = "ingress"; PacketLimit = "3847"; }; + type = types.addCheck (types.attrsOf unitOption) check.network.sectionFlowQueuePIE; + description = lib.mdDoc '' + Each attribute in this set specifies an option in the + `[FlowQueuePIE]` section of the unit. See + {manpage}`systemd.network(5)` for details. + ''; + }; + + stochasticFairBlueConfig = mkOption { + default = {}; + example = { Parent = "ingress"; PacketLimit = "3847"; }; + type = types.addCheck (types.attrsOf unitOption) check.network.sectionStochasticFairBlue; + description = lib.mdDoc '' + Each attribute in this set specifies an option in the + `[StochasticFairBlue]` section of the unit. See + {manpage}`systemd.network(5)` for details. + ''; + }; + + stochasticFairnessQueueingConfig = mkOption { + default = {}; + example = { Parent = "ingress"; PerturbPeriodSec = "30"; }; + type = types.addCheck (types.attrsOf unitOption) check.network.sectionStochasticFairnessQueueing; + description = lib.mdDoc '' + Each attribute in this set specifies an option in the + `[StochasticFairnessQueueing]` section of the unit. See + {manpage}`systemd.network(5)` for details. + ''; + }; + + bfifoConfig = mkOption { + default = {}; + example = { Parent = "ingress"; LimitBytes = "20K"; }; + type = types.addCheck (types.attrsOf unitOption) check.network.sectionBFIFO; + description = lib.mdDoc '' + Each attribute in this set specifies an option in the + `[BFIFO]` section of the unit. See + {manpage}`systemd.network(5)` for details. + ''; + }; + + pfifoConfig = mkOption { + default = {}; + example = { Parent = "ingress"; PacketLimit = "300"; }; + type = types.addCheck (types.attrsOf unitOption) check.network.sectionPFIFO; + description = lib.mdDoc '' + Each attribute in this set specifies an option in the + `[PFIFO]` section of the unit. See + {manpage}`systemd.network(5)` for details. + ''; + }; + + pfifoHeadDropConfig = mkOption { + default = {}; + example = { Parent = "ingress"; PacketLimit = "300"; }; + type = types.addCheck (types.attrsOf unitOption) check.network.sectionPFIFOHeadDrop; + description = lib.mdDoc '' + Each attribute in this set specifies an option in the + `[PFIFOHeadDrop]` section of the unit. See + {manpage}`systemd.network(5)` for details. + ''; + }; + + pfifoFastConfig = mkOption { + default = {}; + example = { Parent = "ingress"; }; + type = types.addCheck (types.attrsOf unitOption) check.network.sectionPFIFOFast; + description = lib.mdDoc '' + Each attribute in this set specifies an option in the + `[PFIFOFast]` section of the unit. See + {manpage}`systemd.network(5)` for details. + ''; + }; + + cakeConfig = mkOption { + default = {}; + example = { Bandwidth = "40M"; OverheadBytes = 8; CompensationMode = "ptm"; }; + type = types.addCheck (types.attrsOf unitOption) check.network.sectionCAKE; + description = lib.mdDoc '' + Each attribute in this set specifies an option in the + `[CAKE]` section of the unit. See + {manpage}`systemd.network(5)` for details. + ''; + }; + + controlledDelayConfig = mkOption { + default = {}; + example = { Parent = "ingress"; TargetSec = "20msec"; }; + type = types.addCheck (types.attrsOf unitOption) check.network.sectionControlledDelay; + description = lib.mdDoc '' + Each attribute in this set specifies an option in the + `[ControlledDelay]` section of the unit. See + {manpage}`systemd.network(5)` for details. + ''; + }; + + deficitRoundRobinSchedulerConfig = mkOption { + default = {}; + example = { Parent = "root"; }; + type = types.addCheck (types.attrsOf unitOption) check.network.sectionDeficitRoundRobinScheduler; + description = lib.mdDoc '' + Each attribute in this set specifies an option in the + `[DeficitRoundRobinScheduler]` section of the unit. See + {manpage}`systemd.network(5)` for details. + ''; + }; + + deficitRoundRobinSchedulerClassConfig = mkOption { + default = {}; + example = { Parent = "root"; QuantumBytes = "300k"; }; + type = types.addCheck (types.attrsOf unitOption) check.network.sectionDeficitRoundRobinSchedulerClass; + description = lib.mdDoc '' + Each attribute in this set specifies an option in the + `[DeficitRoundRobinSchedulerClass]` section of the unit. See + {manpage}`systemd.network(5)` for details. + ''; + }; + + enhancedTransmissionSelectionConfig = mkOption { + default = {}; + example = { Parent = "root"; QuantumBytes = "300k"; Bands = 3; PriorityMap = "100 200 300"; }; + type = types.addCheck (types.attrsOf unitOption) check.network.sectionEnhancedTransmissionSelection; + description = lib.mdDoc '' + Each attribute in this set specifies an option in the + `[EnhancedTransmissionSelection]` section of the unit. See + {manpage}`systemd.network(5)` for details. + ''; + }; + + genericRandomEarlyDetectionConfig = mkOption { + default = {}; + example = { Parent = "root"; VirtualQueues = 5; DefaultVirtualQueue = 3; }; + type = types.addCheck (types.attrsOf unitOption) check.network.sectionGenericRandomEarlyDetection; + description = lib.mdDoc '' + Each attribute in this set specifies an option in the + `[GenericRandomEarlyDetection]` section of the unit. See + {manpage}`systemd.network(5)` for details. + ''; + }; + + fairQueueingControlledDelayConfig = mkOption { + default = {}; + example = { Parent = "root"; Flows = 5; }; + type = types.addCheck (types.attrsOf unitOption) check.network.sectionFairQueueingControlledDelay; + description = lib.mdDoc '' + Each attribute in this set specifies an option in the + `[FairQueueingControlledDelay]` section of the unit. See + {manpage}`systemd.network(5)` for details. + ''; + }; + + fairQueueingConfig = mkOption { + default = {}; + example = { Parent = "root"; FlowLimit = 5; }; + type = types.addCheck (types.attrsOf unitOption) check.network.sectionFairQueueing; + description = lib.mdDoc '' + Each attribute in this set specifies an option in the + `[FairQueueing]` section of the unit. See + {manpage}`systemd.network(5)` for details. + ''; + }; + + trivialLinkEqualizerConfig = mkOption { + default = {}; + example = { Parent = "root"; Id = 0; }; + type = types.addCheck (types.attrsOf unitOption) check.network.sectionTrivialLinkEqualizer; + description = lib.mdDoc '' + Each attribute in this set specifies an option in the + `[TrivialLinkEqualizer]` section of the unit. See + {manpage}`systemd.network(5)` for details. + ''; + }; + + hierarchyTokenBucketConfig = mkOption { + default = {}; + example = { Parent = "root"; }; + type = types.addCheck (types.attrsOf unitOption) check.network.sectionHierarchyTokenBucket; + description = lib.mdDoc '' + Each attribute in this set specifies an option in the + `[HierarchyTokenBucket]` section of the unit. See + {manpage}`systemd.network(5)` for details. + ''; + }; + + hierarchyTokenBucketClassConfig = mkOption { + default = {}; + example = { Parent = "root"; Rate = "10M"; }; + type = types.addCheck (types.attrsOf unitOption) check.network.sectionHierarchyTokenBucketClass; + description = lib.mdDoc '' + Each attribute in this set specifies an option in the + `[HierarchyTokenBucketClass]` section of the unit. See + {manpage}`systemd.network(5)` for details. + ''; + }; + + heavyHitterFilterConfig = mkOption { + default = {}; + example = { Parent = "root"; PacketLimit = 10000; }; + type = types.addCheck (types.attrsOf unitOption) check.network.sectionHeavyHitterFilter; + description = lib.mdDoc '' + Each attribute in this set specifies an option in the + `[HeavyHitterFilter]` section of the unit. See + {manpage}`systemd.network(5)` for details. + ''; + }; + + quickFairQueueingConfig = mkOption { + default = {}; + example = { Parent = "root"; }; + type = types.addCheck (types.attrsOf unitOption) check.network.sectionQuickFairQueueing; + description = lib.mdDoc '' + Each attribute in this set specifies an option in the + `[QuickFairQueueing]` section of the unit. See + {manpage}`systemd.network(5)` for details. + ''; + }; + + quickFairQueueingConfigClass = mkOption { + default = {}; + example = { Parent = "root"; Weight = 133; }; + type = types.addCheck (types.attrsOf unitOption) check.network.sectionQuickFairQueueingClass; + description = lib.mdDoc '' + Each attribute in this set specifies an option in the + `[QuickFairQueueingClass]` section of the unit. See + {manpage}`systemd.network(5)` for details. + ''; + }; + + bridgeVLANs = mkOption { + default = []; + example = [ { bridgeVLANConfig = { VLAN = "10-20"; }; } ]; + type = with types; listOf (submodule bridgeVLANOptions); + description = lib.mdDoc '' + A list of BridgeVLAN sections to be added to the unit. See + {manpage}`systemd.network(5)` for details. + ''; + }; + + name = mkOption { + type = types.nullOr types.str; + default = null; + description = lib.mdDoc '' + The name of the network interface to match against. + ''; + }; + + DHCP = mkOption { + type = types.nullOr types.str; + default = null; + description = lib.mdDoc '' + Whether to enable DHCP on the interfaces matched. + ''; + }; + + domains = mkOption { + type = types.nullOr (types.listOf types.str); + default = null; + description = lib.mdDoc '' + A list of domains to pass to the network config. + ''; + }; + + address = mkOption { + default = [ ]; + type = types.listOf types.str; + description = lib.mdDoc '' + A list of addresses to be added to the network section of the + unit. See {manpage}`systemd.network(5)` for details. + ''; + }; + + gateway = mkOption { + default = [ ]; + type = types.listOf types.str; + description = lib.mdDoc '' + A list of gateways to be added to the network section of the + unit. See {manpage}`systemd.network(5)` for details. + ''; + }; + + dns = mkOption { + default = [ ]; + type = types.listOf types.str; + description = lib.mdDoc '' + A list of dns servers to be added to the network section of the + unit. See {manpage}`systemd.network(5)` for details. + ''; + }; + + ntp = mkOption { + default = [ ]; + type = types.listOf types.str; + description = lib.mdDoc '' + A list of ntp servers to be added to the network section of the + unit. See {manpage}`systemd.network(5)` for details. + ''; + }; + + bridge = mkOption { + default = [ ]; + type = types.listOf types.str; + description = lib.mdDoc '' + A list of bridge interfaces to be added to the network section of the + unit. See {manpage}`systemd.network(5)` for details. + ''; + }; + + bond = mkOption { + default = [ ]; + type = types.listOf types.str; + description = lib.mdDoc '' + A list of bond interfaces to be added to the network section of the + unit. See {manpage}`systemd.network(5)` for details. + ''; + }; + + vrf = mkOption { + default = [ ]; + type = types.listOf types.str; + description = lib.mdDoc '' + A list of vrf interfaces to be added to the network section of the + unit. See {manpage}`systemd.network(5)` for details. + ''; + }; + + vlan = mkOption { + default = [ ]; + type = types.listOf types.str; + description = lib.mdDoc '' + A list of vlan interfaces to be added to the network section of the + unit. See {manpage}`systemd.network(5)` for details. + ''; + }; + + macvlan = mkOption { + default = [ ]; + type = types.listOf types.str; + description = lib.mdDoc '' + A list of macvlan interfaces to be added to the network section of the + unit. See {manpage}`systemd.network(5)` for details. + ''; + }; + + macvtap = mkOption { + default = [ ]; + type = types.listOf types.str; + description = lib.mdDoc '' + A list of macvtap interfaces to be added to the network section of the + unit. See {manpage}`systemd.network(5)` for details. + ''; + }; + + vxlan = mkOption { + default = [ ]; + type = types.listOf types.str; + description = lib.mdDoc '' + A list of vxlan interfaces to be added to the network section of the + unit. See {manpage}`systemd.network(5)` for details. + ''; + }; + + tunnel = mkOption { + default = [ ]; + type = types.listOf types.str; + description = lib.mdDoc '' + A list of tunnel interfaces to be added to the network section of the + unit. See {manpage}`systemd.network(5)` for details. + ''; + }; + + xfrm = mkOption { + default = [ ]; + type = types.listOf types.str; + description = lib.mdDoc '' + A list of xfrm interfaces to be added to the network section of the + unit. See {manpage}`systemd.network(5)` for details. + ''; + }; + + addresses = mkOption { + default = [ ]; + type = with types; listOf (submodule addressOptions); + description = lib.mdDoc '' + A list of address sections to be added to the unit. See + {manpage}`systemd.network(5)` for details. + ''; + }; + + routingPolicyRules = mkOption { + default = [ ]; + type = with types; listOf (submodule routingPolicyRulesOptions); + description = lib.mdDoc '' + A list of routing policy rules sections to be added to the unit. See + {manpage}`systemd.network(5)` for details. + ''; + }; + + routes = mkOption { + default = [ ]; + type = with types; listOf (submodule routeOptions); + description = lib.mdDoc '' + A list of route sections to be added to the unit. See + {manpage}`systemd.network(5)` for details. + ''; + }; + + }; + + networkConfig = { config, ... }: { + config = { + matchConfig = optionalAttrs (config.name != null) { + Name = config.name; + }; + networkConfig = optionalAttrs (config.DHCP != null) { + DHCP = config.DHCP; + } // optionalAttrs (config.domains != null) { + Domains = concatStringsSep " " config.domains; + }; + }; + }; + + networkdConfig = { config, ... }: { + options = { + routeTables = mkOption { + default = {}; + example = { foo = 27; }; + type = with types; attrsOf int; + description = lib.mdDoc '' + Defines route table names as an attrset of name to number. + See {manpage}`networkd.conf(5)` for details. + ''; + }; + + addRouteTablesToIPRoute2 = mkOption { + default = true; + example = false; + type = types.bool; + description = lib.mdDoc '' + If true and routeTables are set, then the specified route tables + will also be installed into /etc/iproute2/rt_tables. + ''; + }; + }; + + config = { + networkConfig = optionalAttrs (config.routeTables != { }) { + RouteTable = mapAttrsToList + (name: number: "${name}:${toString number}") + config.routeTables; + }; + }; + }; + + renderConfig = def: + { text = '' + [Network] + ${attrsToSection def.networkConfig} + '' + + optionalString (def.dhcpV4Config != { }) '' + [DHCPv4] + ${attrsToSection def.dhcpV4Config} + '' + + optionalString (def.dhcpV6Config != { }) '' + [DHCPv6] + ${attrsToSection def.dhcpV6Config} + ''; }; + + mkUnitFiles = prefix: cfg: listToAttrs (map (name: { + name = "${prefix}systemd/network/${name}"; + value.source = "${cfg.units.${name}.unit}/${name}"; + }) (attrNames cfg.units)); + + commonOptions = visible: { + + enable = mkOption { + default = false; + type = types.bool; + description = lib.mdDoc '' + Whether to enable networkd or not. + ''; + }; + + links = mkOption { + default = {}; + inherit visible; + type = with types; attrsOf (submodule [ { options = linkOptions; } ]); + description = lib.mdDoc "Definition of systemd network links."; + }; + + netdevs = mkOption { + default = {}; + inherit visible; + type = with types; attrsOf (submodule [ { options = netdevOptions; } ]); + description = lib.mdDoc "Definition of systemd network devices."; + }; + + networks = mkOption { + default = {}; + inherit visible; + type = with types; attrsOf (submodule [ { options = networkOptions; } networkConfig ]); + description = lib.mdDoc "Definition of systemd networks."; + }; + + config = mkOption { + default = {}; + inherit visible; + type = with types; submodule [ { options = networkdOptions; } networkdConfig ]; + description = lib.mdDoc "Definition of global systemd network config."; + }; + + units = mkOption { + description = lib.mdDoc "Definition of networkd units."; + default = {}; + internal = true; + type = with types; attrsOf (submodule ( + { name, config, ... }: + { options = mapAttrs (_: x: x // { internal = true; }) concreteUnitOptions; + config = { + unit = mkDefault (makeUnit name config); + }; + })); + }; + + wait-online = { + enable = mkOption { + type = types.bool; + default = true; + example = false; + description = lib.mdDoc '' + Whether to enable the systemd-networkd-wait-online service. + + systemd-networkd-wait-online can timeout and fail if there are no network interfaces + available for it to manage. When systemd-networkd is enabled but a different service is + responsible for managing the system's internet connection (for example, NetworkManager or + connman are used to manage WiFi connections), this service is unnecessary and can be + disabled. + ''; + }; + anyInterface = mkOption { + description = lib.mdDoc '' + Whether to consider the network online when any interface is online, as opposed to all of them. + This is useful on portable machines with a wired and a wireless interface, for example. + + This is on by default if {option}`networking.useDHCP` is enabled. + ''; + type = types.bool; + defaultText = "config.networking.useDHCP"; + default = config.networking.useDHCP; + }; + + ignoredInterfaces = mkOption { + description = lib.mdDoc '' + Network interfaces to be ignored when deciding if the system is online. + ''; + type = with types; listOf str; + default = []; + example = [ "wg0" ]; + }; + + timeout = mkOption { + description = lib.mdDoc '' + Time to wait for the network to come online, in seconds. Set to 0 to disable. + ''; + type = types.ints.unsigned; + default = 120; + example = 0; + }; + + extraArgs = mkOption { + description = lib.mdDoc '' + Extra command-line arguments to pass to systemd-networkd-wait-online. + These also affect per-interface `systemd-network-wait-online@` services. + + See {manpage}`systemd-networkd-wait-online.service(8)` for all available options. + ''; + type = with types; listOf str; + default = []; + }; + }; + + }; + + commonConfig = config: let + cfg = config.systemd.network; + mkUnit = f: def: { inherit (def) enable; text = f def; }; + in 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" (mkUnit linkToUnit v)) cfg.links; + + systemd.network.wait-online.extraArgs = + [ "--timeout=${toString cfg.wait-online.timeout}" ] + ++ optional cfg.wait-online.anyInterface "--any" + ++ map (i: "--ignore=${i}") cfg.wait-online.ignoredInterfaces; + } + + (mkIf config.systemd.network.enable { + + systemd.network.units = mapAttrs' (n: v: nameValuePair "${n}.netdev" (mkUnit netdevToUnit v)) cfg.netdevs + // mapAttrs' (n: v: nameValuePair "${n}.network" (mkUnit networkToUnit v)) cfg.networks; + + # systemd-networkd is socket-activated by kernel netlink route change + # messages. It is important to have systemd buffer those on behalf of + # networkd. + systemd.sockets.systemd-networkd.wantedBy = [ "sockets.target" ]; + + systemd.services.systemd-networkd-wait-online = { + inherit (cfg.wait-online) enable; + wantedBy = [ "network-online.target" ]; + serviceConfig.ExecStart = [ + "" + "${config.systemd.package}/lib/systemd/systemd-networkd-wait-online ${utils.escapeSystemdExecArgs cfg.wait-online.extraArgs}" + ]; + }; + + 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 ${utils.escapeSystemdExecArgs cfg.wait-online.extraArgs}"; + }; + }; + + }) + ]; + + stage2Config = let + cfg = config.systemd.network; + unitFiles = mkUnitFiles "" cfg; + in mkMerge [ + (commonConfig config) + + { environment.etc = unitFiles; } + + (mkIf config.systemd.network.enable { + + users.users.systemd-network.group = "systemd-network"; + + systemd.additionalUpstreamSystemUnits = [ + "systemd-networkd-wait-online.service" + "systemd-networkd.service" + "systemd-networkd.socket" + ]; + + environment.etc."systemd/networkd.conf" = renderConfig cfg.config; + + systemd.services.systemd-networkd = let + isReloadableUnitFileName = unitFileName: strings.hasSuffix ".network" unitFileName; + reloadableUnitFiles = attrsets.filterAttrs (k: v: isReloadableUnitFileName k) unitFiles; + nonReloadableUnitFiles = attrsets.filterAttrs (k: v: !isReloadableUnitFileName k) unitFiles; + unitFileSources = unitFiles: map (x: x.source) (attrValues unitFiles); + in { + wantedBy = [ "multi-user.target" ]; + reloadTriggers = unitFileSources reloadableUnitFiles; + restartTriggers = unitFileSources nonReloadableUnitFiles ++ [ + config.environment.etc."systemd/networkd.conf".source + ]; + aliases = [ "dbus-org.freedesktop.network1.service" ]; + }; + + networking.iproute2 = mkIf (cfg.config.addRouteTablesToIPRoute2 && cfg.config.routeTables != { }) { + enable = mkDefault true; + rttablesExtraConfig = '' + + # Extra tables defined in NixOS systemd.networkd.config.routeTables. + ${concatStringsSep "\n" (mapAttrsToList (name: number: "${toString number} ${name}") cfg.config.routeTables)} + ''; + }; + + services.resolved.enable = mkDefault true; + + }) + ]; + + stage1Options = { + options.boot.initrd.systemd.network.networks = mkOption { + type = with types; attrsOf (submodule { + # Default in initrd is dhcp-on-stop, which is correct if flushBeforeStage2 = false + config = mkIf config.boot.initrd.network.flushBeforeStage2 { + networkConfig.KeepConfiguration = mkDefault false; + }; + }); + }; + }; + + stage1Config = let + cfg = config.boot.initrd.systemd.network; + in mkMerge [ + (commonConfig config.boot.initrd) + + { + systemd.network.enable = mkDefault config.boot.initrd.network.enable; + systemd.contents = mkUnitFiles "/etc/" cfg; + + # Networkd link files are used early by udev to set up interfaces early. + # This must be done in stage 1 to avoid race conditions between udev and + # network daemons. + systemd.network.units = lib.filterAttrs (n: _: hasSuffix ".link" n) config.systemd.network.units; + systemd.storePaths = ["${config.boot.initrd.systemd.package}/lib/systemd/network/99-default.link"]; + } + + (mkIf cfg.enable { + + # For networkctl + systemd.dbus.enable = mkDefault true; + + systemd.additionalUpstreamUnits = [ + "systemd-networkd-wait-online.service" + "systemd-networkd.service" + "systemd-networkd.socket" + "systemd-network-generator.service" + "network-online.target" + "network-pre.target" + "network.target" + "nss-lookup.target" + "nss-user-lookup.target" + "remote-fs-pre.target" + "remote-fs.target" + ]; + systemd.users.systemd-network = {}; + systemd.groups.systemd-network = {}; + + systemd.contents."/etc/systemd/networkd.conf" = renderConfig cfg.config; + + systemd.services.systemd-networkd = { + wantedBy = [ "initrd.target" ]; + }; + systemd.sockets.systemd-networkd = { + wantedBy = [ "initrd.target" ]; + }; + + systemd.services.systemd-network-generator.wantedBy = [ "sysinit.target" ]; + + systemd.storePaths = [ + "${config.boot.initrd.systemd.package}/lib/systemd/systemd-networkd" + "${config.boot.initrd.systemd.package}/lib/systemd/systemd-networkd-wait-online" + "${config.boot.initrd.systemd.package}/lib/systemd/systemd-network-generator" + ]; + kernelModules = [ "af_packet" ]; + + }) + ]; + +in + +{ + imports = [ stage1Options ]; + + options = { + systemd.network = commonOptions true; + boot.initrd.systemd.network = commonOptions "shallow"; + }; + + config = mkMerge [ + stage2Config + (mkIf config.boot.initrd.systemd.enable { + assertions = [{ + assertion = !config.boot.initrd.network.udhcpc.enable && config.boot.initrd.network.udhcpc.extraArgs == []; + message = '' + systemd stage 1 networking does not support 'boot.initrd.network.udhcpc'. Configure + DHCP with 'networking.*' options or with 'boot.initrd.systemd.network' options. + ''; + }]; + + boot.initrd = stage1Config; + }) + ]; +} diff --git a/nixpkgs/nixos/modules/system/boot/pbkdf2-sha512.c b/nixpkgs/nixos/modules/system/boot/pbkdf2-sha512.c new file mode 100644 index 000000000000..67e989957ba6 --- /dev/null +++ b/nixpkgs/nixos/modules/system/boot/pbkdf2-sha512.c @@ -0,0 +1,38 @@ +#include <stdint.h> +#include <string.h> +#include <stdio.h> +#include <openssl/evp.h> + +void hextorb(uint8_t* hex, uint8_t* rb) +{ + while(sscanf(hex, "%2x", rb) == 1) + { + hex += 2; + rb += 1; + } + *rb = '\0'; +} + +int main(int argc, char** argv) +{ + uint8_t k_user[2048]; + uint8_t salt[2048]; + uint8_t key[4096]; + + uint32_t key_length = atoi(argv[1]); + uint32_t iteration_count = atoi(argv[2]); + + hextorb(argv[3], salt); + uint32_t salt_length = strlen(argv[3]) / 2; + + fgets(k_user, 2048, stdin); + uint32_t k_user_length = strlen(k_user); + if(k_user[k_user_length - 1] == '\n') { + k_user[k_user_length - 1] = '\0'; + } + + PKCS5_PBKDF2_HMAC(k_user, k_user_length, salt, salt_length, iteration_count, EVP_sha512(), key_length, key); + fwrite(key, 1, key_length, stdout); + + return 0; +} diff --git a/nixpkgs/nixos/modules/system/boot/plymouth.nix b/nixpkgs/nixos/modules/system/boot/plymouth.nix new file mode 100644 index 000000000000..b041b8951fa3 --- /dev/null +++ b/nixpkgs/nixos/modules/system/boot/plymouth.nix @@ -0,0 +1,349 @@ +{ config, lib, options, pkgs, ... }: + +with lib; + +let + + inherit (pkgs) nixos-icons; + plymouth = pkgs.plymouth.override { + systemd = config.boot.initrd.systemd.package; + }; + + cfg = config.boot.plymouth; + opt = options.boot.plymouth; + + nixosBreezePlymouth = pkgs.plasma5Packages.breeze-plymouth.override { + logoFile = cfg.logo; + logoName = "nixos"; + osName = "NixOS"; + osVersion = config.system.nixos.release; + }; + + plymouthLogos = pkgs.runCommand "plymouth-logos" { inherit (cfg) logo; } '' + mkdir -p $out + + # For themes that are compiled with PLYMOUTH_LOGO_FILE + mkdir -p $out/etc/plymouth + ln -s $logo $out/etc/plymouth/logo.png + + # Logo for bgrt theme + # Note this is technically an abuse of watermark for the bgrt theme + # See: https://gitlab.freedesktop.org/plymouth/plymouth/-/issues/95#note_813768 + mkdir -p $out/share/plymouth/themes/spinner + ln -s $logo $out/share/plymouth/themes/spinner/watermark.png + + # Logo for spinfinity theme + # See: https://gitlab.freedesktop.org/plymouth/plymouth/-/issues/106 + mkdir -p $out/share/plymouth/themes/spinfinity + ln -s $logo $out/share/plymouth/themes/spinfinity/header-image.png + ''; + + themesEnv = pkgs.buildEnv { + name = "plymouth-themes"; + paths = [ + plymouth + plymouthLogos + ] ++ cfg.themePackages; + }; + + configFile = pkgs.writeText "plymouthd.conf" '' + [Daemon] + ShowDelay=0 + DeviceTimeout=8 + Theme=${cfg.theme} + ${cfg.extraConfig} + ''; + +in + +{ + + options = { + + boot.plymouth = { + + enable = mkEnableOption (lib.mdDoc "Plymouth boot splash screen"); + + font = mkOption { + default = "${pkgs.dejavu_fonts.minimal}/share/fonts/truetype/DejaVuSans.ttf"; + defaultText = literalExpression ''"''${pkgs.dejavu_fonts.minimal}/share/fonts/truetype/DejaVuSans.ttf"''; + type = types.path; + description = lib.mdDoc '' + Font file made available for displaying text on the splash screen. + ''; + }; + + themePackages = mkOption { + default = lib.optional (cfg.theme == "breeze") nixosBreezePlymouth; + defaultText = literalMD '' + A NixOS branded variant of the breeze theme when + `config.${opt.theme} == "breeze"`, otherwise + `[ ]`. + ''; + type = types.listOf types.package; + description = lib.mdDoc '' + Extra theme packages for plymouth. + ''; + }; + + theme = mkOption { + default = "bgrt"; + type = types.str; + description = lib.mdDoc '' + Splash screen theme. + ''; + }; + + logo = mkOption { + type = types.path; + # Dimensions are 48x48 to match GDM logo + default = "${nixos-icons}/share/icons/hicolor/48x48/apps/nix-snowflake-white.png"; + defaultText = literalExpression ''"''${nixos-icons}/share/icons/hicolor/48x48/apps/nix-snowflake-white.png"''; + example = literalExpression '' + pkgs.fetchurl { + url = "https://nixos.org/logo/nixos-hires.png"; + sha256 = "1ivzgd7iz0i06y36p8m5w48fd8pjqwxhdaavc0pxs7w1g7mcy5si"; + } + ''; + description = lib.mdDoc '' + Logo which is displayed on the splash screen. + ''; + }; + + extraConfig = mkOption { + type = types.lines; + default = ""; + description = lib.mdDoc '' + Literal string to append to `configFile` + and the config file generated by the plymouth module. + ''; + }; + + }; + + }; + + config = mkIf cfg.enable { + + boot.kernelParams = [ "splash" ]; + + # To be discoverable by systemd. + environment.systemPackages = [ plymouth ]; + + environment.etc."plymouth/plymouthd.conf".source = configFile; + environment.etc."plymouth/plymouthd.defaults".source = "${plymouth}/share/plymouth/plymouthd.defaults"; + environment.etc."plymouth/logo.png".source = cfg.logo; + environment.etc."plymouth/themes".source = "${themesEnv}/share/plymouth/themes"; + # XXX: Needed because we supply a different set of plugins in initrd. + environment.etc."plymouth/plugins".source = "${plymouth}/lib/plymouth"; + + systemd.tmpfiles.rules = [ + "d /run/plymouth 0755 root root 0 -" + "L+ /run/plymouth/plymouthd.defaults - - - - /etc/plymouth/plymouthd.defaults" + "L+ /run/plymouth/themes - - - - /etc/plymouth/themes" + "L+ /run/plymouth/plugins - - - - /etc/plymouth/plugins" + ]; + + systemd.packages = [ plymouth ]; + + systemd.services.plymouth-kexec.wantedBy = [ "kexec.target" ]; + systemd.services.plymouth-halt.wantedBy = [ "halt.target" ]; + systemd.services.plymouth-quit-wait.wantedBy = [ "multi-user.target" ]; + systemd.services.plymouth-quit.wantedBy = [ "multi-user.target" ]; + systemd.services.plymouth-poweroff.wantedBy = [ "poweroff.target" ]; + systemd.services.plymouth-reboot.wantedBy = [ "reboot.target" ]; + systemd.services.plymouth-read-write.wantedBy = [ "sysinit.target" ]; + systemd.services.systemd-ask-password-plymouth.wantedBy = [ "multi-user.target" ]; + systemd.paths.systemd-ask-password-plymouth.wantedBy = [ "multi-user.target" ]; + + # Prevent Plymouth taking over the screen during system updates. + systemd.services.plymouth-start.restartIfChanged = false; + + boot.initrd.systemd = { + extraBin.plymouth = "${plymouth}/bin/plymouth"; # for the recovery shell + storePaths = [ + "${lib.getBin config.boot.initrd.systemd.package}/bin/systemd-tty-ask-password-agent" + "${plymouth}/bin/plymouthd" + "${plymouth}/sbin/plymouthd" + ]; + packages = [ plymouth ]; # systemd units + contents = { + # Files + "/etc/plymouth/plymouthd.conf".source = configFile; + "/etc/plymouth/logo.png".source = cfg.logo; + "/etc/plymouth/plymouthd.defaults".source = "${plymouth}/share/plymouth/plymouthd.defaults"; + # Directories + "/etc/plymouth/plugins".source = pkgs.runCommand "plymouth-initrd-plugins" {} '' + # Check if the actual requested theme is here + if [[ ! -d ${themesEnv}/share/plymouth/themes/${cfg.theme} ]]; then + echo "The requested theme: ${cfg.theme} is not provided by any of the packages in boot.plymouth.themePackages" + exit 1 + fi + + moduleName="$(sed -n 's,ModuleName *= *,,p' ${themesEnv}/share/plymouth/themes/${cfg.theme}/${cfg.theme}.plymouth)" + + mkdir -p $out/renderers + # module might come from a theme + cp ${themesEnv}/lib/plymouth/*.so $out + cp ${plymouth}/lib/plymouth/renderers/*.so $out/renderers + ''; + "/etc/plymouth/themes".source = pkgs.runCommand "plymouth-initrd-themes" {} '' + # Check if the actual requested theme is here + if [[ ! -d ${themesEnv}/share/plymouth/themes/${cfg.theme} ]]; then + echo "The requested theme: ${cfg.theme} is not provided by any of the packages in boot.plymouth.themePackages" + exit 1 + fi + + mkdir -p $out/${cfg.theme} + cp -r ${themesEnv}/share/plymouth/themes/${cfg.theme}/* $out/${cfg.theme} + # Copy more themes if the theme depends on others + for theme in $(grep -hRo '/share/plymouth/themes/.*$' $out | xargs -n1 basename); do + if [[ -d "${themesEnv}/share/plymouth/themes/$theme" ]]; then + if [[ ! -d "$out/$theme" ]]; then + echo "Adding dependent theme: $theme" + mkdir -p "$out/$theme" + cp -r "${themesEnv}/share/plymouth/themes/$theme"/* "$out/$theme" + fi + else + echo "Missing theme dependency: $theme" + fi + done + # Fixup references + for theme in $out/*/*.plymouth; do + sed -i "s,${builtins.storeDir}/.*/share/plymouth/themes,$out," "$theme" + done + ''; + + # Fonts + "/etc/plymouth/fonts".source = pkgs.runCommand "plymouth-initrd-fonts" {} '' + mkdir -p $out + cp ${cfg.font} $out + ''; + "/etc/fonts/fonts.conf".text = '' + <?xml version="1.0"?> + <!DOCTYPE fontconfig SYSTEM "urn:fontconfig:fonts.dtd"> + <fontconfig> + <dir>/etc/plymouth/fonts</dir> + </fontconfig> + ''; + }; + # Properly enable units. These are the units that arch copies + services = { + plymouth-halt.wantedBy = [ "halt.target" ]; + plymouth-kexec.wantedBy = [ "kexec.target" ]; + plymouth-poweroff.wantedBy = [ "poweroff.target" ]; + plymouth-quit-wait.wantedBy = [ "multi-user.target" ]; + plymouth-quit.wantedBy = [ "multi-user.target" ]; + plymouth-read-write.wantedBy = [ "sysinit.target" ]; + plymouth-reboot.wantedBy = [ "reboot.target" ]; + plymouth-start.wantedBy = [ "initrd-switch-root.target" "sysinit.target" ]; + plymouth-switch-root-initramfs.wantedBy = [ "halt.target" "kexec.target" "plymouth-switch-root-initramfs.service" "poweroff.target" "reboot.target" ]; + plymouth-switch-root.wantedBy = [ "initrd-switch-root.target" ]; + }; + # Link in runtime files before starting + services.plymouth-start.preStart = '' + mkdir -p /run/plymouth + ln -sf /etc/plymouth/{plymouthd.defaults,themes,plugins} /run/plymouth/ + ''; + }; + + # Insert required udev rules. We take stage 2 systemd because the udev + # rules are only generated when building with logind. + boot.initrd.services.udev.packages = [ (pkgs.runCommand "initrd-plymouth-udev-rules" {} '' + mkdir -p $out/etc/udev/rules.d + cp ${config.systemd.package.out}/lib/udev/rules.d/{70-uaccess,71-seat}.rules $out/etc/udev/rules.d + sed -i '/loginctl/d' $out/etc/udev/rules.d/71-seat.rules + '') ]; + + boot.initrd.extraUtilsCommands = lib.mkIf (!config.boot.initrd.systemd.enable) '' + copy_bin_and_libs ${plymouth}/bin/plymouth + copy_bin_and_libs ${plymouth}/bin/plymouthd + + # Check if the actual requested theme is here + if [[ ! -d ${themesEnv}/share/plymouth/themes/${cfg.theme} ]]; then + echo "The requested theme: ${cfg.theme} is not provided by any of the packages in boot.plymouth.themePackages" + exit 1 + fi + + moduleName="$(sed -n 's,ModuleName *= *,,p' ${themesEnv}/share/plymouth/themes/${cfg.theme}/${cfg.theme}.plymouth)" + + mkdir -p $out/lib/plymouth/renderers + # module might come from a theme + cp ${themesEnv}/lib/plymouth/*.so $out/lib/plymouth + cp ${plymouth}/lib/plymouth/renderers/*.so $out/lib/plymouth/renderers + + mkdir -p $out/share/plymouth/themes + cp ${plymouth}/share/plymouth/plymouthd.defaults $out/share/plymouth + + # Copy themes into working directory for patching + mkdir themes + + # Use -L to copy the directories proper, not the symlinks to them. + # Copy all themes because they're not large assets, and bgrt depends on the ImageDir of + # the spinner theme. + cp -r -L ${themesEnv}/share/plymouth/themes/* themes + + # Patch out any attempted references to the theme or plymouth's themes directory + chmod -R +w themes + find themes -type f | while read file + do + sed -i "s,${builtins.storeDir}/.*/share/plymouth/themes,$out/share/plymouth/themes,g" $file + done + + # Install themes + cp -r themes/* $out/share/plymouth/themes + + # Install logo + mkdir -p $out/etc/plymouth + cp -r -L ${themesEnv}/etc/plymouth $out/etc + + # Setup font + mkdir -p $out/share/fonts + cp ${cfg.font} $out/share/fonts + mkdir -p $out/etc/fonts + cat > $out/etc/fonts/fonts.conf <<EOF + <?xml version="1.0"?> + <!DOCTYPE fontconfig SYSTEM "urn:fontconfig:fonts.dtd"> + <fontconfig> + <dir>$out/share/fonts</dir> + </fontconfig> + EOF + ''; + + boot.initrd.extraUtilsCommandsTest = mkIf (!config.boot.initrd.systemd.enable) '' + $out/bin/plymouthd --help >/dev/null + $out/bin/plymouth --help >/dev/null + ''; + + boot.initrd.extraUdevRulesCommands = mkIf (!config.boot.initrd.systemd.enable) '' + cp ${config.systemd.package}/lib/udev/rules.d/{70-uaccess,71-seat}.rules $out + sed -i '/loginctl/d' $out/71-seat.rules + ''; + + # We use `mkAfter` to ensure that LUKS password prompt would be shown earlier than the splash screen. + boot.initrd.preLVMCommands = mkIf (!config.boot.initrd.systemd.enable) (mkAfter '' + mkdir -p /etc/plymouth + mkdir -p /run/plymouth + ln -s $extraUtils/etc/plymouth/logo.png /etc/plymouth/logo.png + ln -s ${configFile} /etc/plymouth/plymouthd.conf + ln -s $extraUtils/share/plymouth/plymouthd.defaults /run/plymouth/plymouthd.defaults + ln -s $extraUtils/share/plymouth/themes /run/plymouth/themes + ln -s $extraUtils/lib/plymouth /run/plymouth/plugins + ln -s $extraUtils/etc/fonts /etc/fonts + + plymouthd --mode=boot --pid-file=/run/plymouth/pid --attach-to-session + plymouth show-splash + ''); + + boot.initrd.postMountCommands = mkIf (!config.boot.initrd.systemd.enable) '' + plymouth update-root-fs --new-root-dir="$targetRoot" + ''; + + # `mkBefore` to ensure that any custom prompts would be visible. + boot.initrd.preFailCommands = mkIf (!config.boot.initrd.systemd.enable) (mkBefore '' + plymouth quit --wait + ''); + + }; + +} diff --git a/nixpkgs/nixos/modules/system/boot/resolved.nix b/nixpkgs/nixos/modules/system/boot/resolved.nix new file mode 100644 index 000000000000..c42c88163c56 --- /dev/null +++ b/nixpkgs/nixos/modules/system/boot/resolved.nix @@ -0,0 +1,185 @@ +{ config, lib, pkgs, ... }: + +with lib; +let + cfg = config.services.resolved; + + dnsmasqResolve = config.services.dnsmasq.enable && + config.services.dnsmasq.resolveLocalQueries; + +in +{ + + options = { + + services.resolved.enable = mkOption { + default = false; + type = types.bool; + description = lib.mdDoc '' + Whether to enable the systemd DNS resolver daemon, `systemd-resolved`. + + Search for `services.resolved` to see all options. + ''; + }; + + services.resolved.fallbackDns = mkOption { + default = null; + example = [ "8.8.8.8" "2001:4860:4860::8844" ]; + type = types.nullOr (types.listOf types.str); + description = lib.mdDoc '' + A list of IPv4 and IPv6 addresses to use as the fallback DNS servers. + If this option is null, a compiled-in list of DNS servers is used instead. + Setting this option to an empty list will override the built-in list to an empty list, disabling fallback. + ''; + }; + + services.resolved.domains = mkOption { + default = config.networking.search; + defaultText = literalExpression "config.networking.search"; + example = [ "example.com" ]; + type = types.listOf types.str; + description = lib.mdDoc '' + A list of domains. These domains are used as search suffixes + when resolving single-label host names (domain names which + contain no dot), in order to qualify them into fully-qualified + domain names (FQDNs). + + For compatibility reasons, if this setting is not specified, + the search domains listed in + {file}`/etc/resolv.conf` are used instead, if + that file exists and any domains are configured in it. + ''; + }; + + services.resolved.llmnr = mkOption { + default = "true"; + example = "false"; + type = types.enum [ "true" "resolve" "false" ]; + description = lib.mdDoc '' + Controls Link-Local Multicast Name Resolution support + (RFC 4795) on the local host. + + If set to + - `"true"`: Enables full LLMNR responder and resolver support. + - `"false"`: Disables both. + - `"resolve"`: Only resolution support is enabled, but responding is disabled. + ''; + }; + + services.resolved.dnssec = mkOption { + default = "false"; + example = "true"; + type = types.enum [ "true" "allow-downgrade" "false" ]; + description = lib.mdDoc '' + If set to + - `"true"`: + all DNS lookups are DNSSEC-validated locally (excluding + LLMNR and Multicast DNS). Note that this mode requires a + DNS server that supports DNSSEC. If the DNS server does + not properly support DNSSEC all validations will fail. + - `"allow-downgrade"`: + DNSSEC validation is attempted, but if the server does not + support DNSSEC properly, DNSSEC mode is automatically + disabled. Note that this mode makes DNSSEC validation + vulnerable to "downgrade" attacks, where an attacker might + be able to trigger a downgrade to non-DNSSEC mode by + synthesizing a DNS response that suggests DNSSEC was not + supported. + - `"false"`: DNS lookups are not DNSSEC validated. + + At the time of September 2023, systemd upstream advise + to disable DNSSEC by default as the current code + is not robust enough to deal with "in the wild" non-compliant + servers, which will usually give you a broken bad experience + in addition of insecure. + ''; + }; + + services.resolved.dnsovertls = mkOption { + default = "false"; + example = "true"; + type = types.enum [ "true" "opportunistic" "false" ]; + description = lib.mdDoc '' + If set to + - `"true"`: + all DNS lookups will be encrypted. This requires + that the DNS server supports DNS-over-TLS and + has a valid certificate. If the hostname was specified + via the `address#hostname` format in {option}`services.resolved.domains` + then the specified hostname is used to validate its certificate. + - `"opportunistic"`: + all DNS lookups will attempt to be encrypted, but will fallback + to unecrypted requests if the server does not support DNS-over-TLS. + Note that this mode does allow for a malicious party to conduct a + downgrade attack by immitating the DNS server and pretending to not + support encryption. + - `"false"`: + all DNS lookups are done unencrypted. + ''; + }; + + services.resolved.extraConfig = mkOption { + default = ""; + type = types.lines; + description = lib.mdDoc '' + Extra config to append to resolved.conf. + ''; + }; + + }; + + config = mkIf cfg.enable { + + assertions = [ + { assertion = !config.networking.useHostResolvConf; + message = "Using host resolv.conf is not supported with systemd-resolved"; + } + ]; + + users.users.systemd-resolve.group = "systemd-resolve"; + + # add resolve to nss hosts database if enabled and nscd enabled + # system.nssModules is configured in nixos/modules/system/boot/systemd.nix + # added with order 501 to allow modules to go before with mkBefore + system.nssDatabases.hosts = (mkOrder 501 ["resolve [!UNAVAIL=return]"]); + + systemd.additionalUpstreamSystemUnits = [ + "systemd-resolved.service" + ]; + + systemd.services.systemd-resolved = { + wantedBy = [ "multi-user.target" ]; + aliases = [ "dbus-org.freedesktop.resolve1.service" ]; + restartTriggers = [ config.environment.etc."systemd/resolved.conf".source ]; + }; + + environment.etc = { + "systemd/resolved.conf".text = '' + [Resolve] + ${optionalString (config.networking.nameservers != []) + "DNS=${concatStringsSep " " config.networking.nameservers}"} + ${optionalString (cfg.fallbackDns != null) + "FallbackDNS=${concatStringsSep " " cfg.fallbackDns}"} + ${optionalString (cfg.domains != []) + "Domains=${concatStringsSep " " cfg.domains}"} + LLMNR=${cfg.llmnr} + DNSSEC=${cfg.dnssec} + DNSOverTLS=${cfg.dnsovertls} + ${config.services.resolved.extraConfig} + ''; + + # symlink the dynamic stub resolver of resolv.conf as recommended by upstream: + # https://www.freedesktop.org/software/systemd/man/systemd-resolved.html#/etc/resolv.conf + "resolv.conf".source = "/run/systemd/resolve/stub-resolv.conf"; + } // optionalAttrs dnsmasqResolve { + "dnsmasq-resolv.conf".source = "/run/systemd/resolve/resolv.conf"; + }; + + # If networkmanager is enabled, ask it to interface with resolved. + networking.networkmanager.dns = "systemd-resolved"; + + networking.resolvconf.package = pkgs.systemd; + + }; + +} diff --git a/nixpkgs/nixos/modules/system/boot/shutdown.nix b/nixpkgs/nixos/modules/system/boot/shutdown.nix new file mode 100644 index 000000000000..8cda7b3aabe8 --- /dev/null +++ b/nixpkgs/nixos/modules/system/boot/shutdown.nix @@ -0,0 +1,27 @@ +{ config, lib, pkgs, ... }: + +with lib; + +{ + + # This unit saves the value of the system clock to the hardware + # clock on shutdown. + systemd.services.save-hwclock = + { description = "Save Hardware Clock"; + + wantedBy = [ "shutdown.target" ]; + + unitConfig = { + DefaultDependencies = false; + ConditionPathExists = "/dev/rtc"; + }; + + serviceConfig = { + Type = "oneshot"; + ExecStart = "${pkgs.util-linux}/sbin/hwclock --systohc ${if config.time.hardwareClockInLocalTime then "--localtime" else "--utc"}"; + }; + }; + + boot.kernel.sysctl."kernel.poweroff_cmd" = "${config.systemd.package}/sbin/poweroff"; + +} diff --git a/nixpkgs/nixos/modules/system/boot/stage-1-init.sh b/nixpkgs/nixos/modules/system/boot/stage-1-init.sh new file mode 100644 index 000000000000..59cf1a47fb7f --- /dev/null +++ b/nixpkgs/nixos/modules/system/boot/stage-1-init.sh @@ -0,0 +1,669 @@ +#! @shell@ + +targetRoot=/mnt-root +console=tty1 +verbose="@verbose@" + +info() { + if [[ -n "$verbose" ]]; then + echo "$@" + fi +} + +extraUtils="@extraUtils@" +export LD_LIBRARY_PATH=@extraUtils@/lib +export PATH=@extraUtils@/bin +ln -s @extraUtils@/bin /bin +# hardcoded in util-linux's mount helper search path `/run/wrappers/bin:/run/current-system/sw/bin:/sbin` +ln -s @extraUtils@/bin /sbin + +# Copy the secrets to their needed location +if [ -d "@extraUtils@/secrets" ]; then + for secret in $(cd "@extraUtils@/secrets"; find . -type f); do + mkdir -p $(dirname "/$secret") + ln -s "@extraUtils@/secrets/$secret" "$secret" + done +fi + +# Stop LVM complaining about fd3 +export LVM_SUPPRESS_FD_WARNINGS=true + +fail() { + if [ -n "$panicOnFail" ]; then exit 1; fi + + @preFailCommands@ + + # If starting stage 2 failed, allow the user to repair the problem + # in an interactive shell. + cat <<EOF + +An error occurred in stage 1 of the boot process, which must mount the +root filesystem on \`$targetRoot' and then start stage 2. Press one +of the following keys: + +EOF + if [ -n "$allowShell" ]; then cat <<EOF + i) to launch an interactive shell + f) to start an interactive shell having pid 1 (needed if you want to + start stage 2's init manually) +EOF + fi + cat <<EOF + r) to reboot immediately + *) to ignore the error and continue +EOF + + read -n 1 reply + + if [ -n "$allowShell" -a "$reply" = f ]; then + exec setsid @shell@ -c "exec @shell@ < /dev/$console >/dev/$console 2>/dev/$console" + elif [ -n "$allowShell" -a "$reply" = i ]; then + echo "Starting interactive shell..." + setsid @shell@ -c "exec @shell@ < /dev/$console >/dev/$console 2>/dev/$console" || fail + elif [ "$reply" = r ]; then + echo "Rebooting..." + reboot -f + else + info "Continuing..." + fi +} + +trap 'fail' 0 + + +# Print a greeting. +info +info "[1;32m<<< @distroName@ Stage 1 >>>[0m" +info + +# Make several required directories. +mkdir -p /etc/udev +touch /etc/fstab # to shut up mount +ln -s /proc/mounts /etc/mtab # to shut up mke2fs +touch /etc/udev/hwdb.bin # to shut up udev +touch /etc/initrd-release + +# Function for waiting for device(s) to appear. +waitDevice() { + local device="$1" + # Split device string using ':' as a delimiter, bcachefs uses + # this for multi-device filesystems, i.e. /dev/sda1:/dev/sda2:/dev/sda3 + local IFS + + # bcachefs is the only known use for this at the moment + # Preferably, the 'UUID=' syntax should be enforced, but + # this is kept for compatibility reasons + if [ "$fsType" = bcachefs ]; then IFS=':'; fi + + # USB storage devices tend to appear with some delay. It would be + # great if we had a way to synchronously wait for them, but + # alas... So just wait for a few seconds for the device to + # appear. + for dev in $device; do + if test ! -e $dev; then + echo -n "waiting for device $dev to appear..." + try=20 + while [ $try -gt 0 ]; do + sleep 1 + # also re-try lvm activation now that new block devices might have appeared + lvm vgchange -ay + # and tell udev to create nodes for the new LVs + udevadm trigger --action=add + if test -e $dev; then break; fi + echo -n "." + try=$((try - 1)) + done + echo + [ $try -ne 0 ] + fi + done +} + +# Create the mount point if required. +makeMountPoint() { + local device="$1" + local mountPoint="$2" + local options="$3" + + local IFS=, + + # If we're bind mounting a file, the mount point should also be a file. + if ! [ -d "$device" ]; then + for opt in $options; do + if [ "$opt" = bind ] || [ "$opt" = rbind ]; then + mkdir -p "$(dirname "/mnt-root$mountPoint")" + touch "/mnt-root$mountPoint" + return + fi + done + fi + + mkdir -m 0755 -p "/mnt-root$mountPoint" +} + +# Mount special file systems. +specialMount() { + local device="$1" + local mountPoint="$2" + local options="$3" + local fsType="$4" + + mkdir -m 0755 -p "$mountPoint" + mount -n -t "$fsType" -o "$options" "$device" "$mountPoint" +} +source @earlyMountScript@ + +# Copy initrd secrets from /.initrd-secrets to their actual destinations +if [ -d "/.initrd-secrets" ]; then + # + # Secrets are named by their full destination pathname and stored + # under /.initrd-secrets/ + # + for secret in $(cd "/.initrd-secrets"; find . -type f); do + mkdir -p $(dirname "/$secret") + cp "/.initrd-secrets/$secret" "$secret" + done +fi + +# Log the script output to /dev/kmsg or /run/log/stage-1-init.log. +mkdir -p /tmp +mkfifo /tmp/stage-1-init.log.fifo +logOutFd=8 && logErrFd=9 +eval "exec $logOutFd>&1 $logErrFd>&2" +if test -w /dev/kmsg; then + tee -i < /tmp/stage-1-init.log.fifo /proc/self/fd/"$logOutFd" | while read -r line; do + if test -n "$line"; then + echo "<7>stage-1-init: [$(date)] $line" > /dev/kmsg + fi + done & +else + mkdir -p /run/log + tee -i < /tmp/stage-1-init.log.fifo /run/log/stage-1-init.log & +fi +exec > /tmp/stage-1-init.log.fifo 2>&1 + + +# Process the kernel command line. +export stage2Init=/init +for o in $(cat /proc/cmdline); do + case $o in + console=*) + set -- $(IFS==; echo $o) + params=$2 + set -- $(IFS=,; echo $params) + console=$1 + ;; + init=*) + set -- $(IFS==; echo $o) + stage2Init=$2 + ;; + boot.persistence=*) + set -- $(IFS==; echo $o) + persistence=$2 + ;; + boot.persistence.opt=*) + set -- $(IFS==; echo $o) + persistence_opt=$2 + ;; + boot.trace|debugtrace) + # Show each command. + set -x + ;; + boot.shell_on_fail) + allowShell=1 + ;; + boot.debug1|debug1) # stop right away + allowShell=1 + fail + ;; + boot.debug1devices) # stop after loading modules and creating device nodes + allowShell=1 + debug1devices=1 + ;; + boot.debug1mounts) # stop after mounting file systems + allowShell=1 + debug1mounts=1 + ;; + boot.panic_on_fail|stage1panic=1) + panicOnFail=1 + ;; + root=*) + # If a root device is specified on the kernel command + # line, make it available through the symlink /dev/root. + # Recognise LABEL= and UUID= to support UNetbootin. + set -- $(IFS==; echo $o) + if [ $2 = "LABEL" ]; then + root="/dev/disk/by-label/$3" + elif [ $2 = "UUID" ]; then + root="/dev/disk/by-uuid/$3" + else + root=$2 + fi + ln -s "$root" /dev/root + ;; + copytoram) + copytoram=1 + ;; + findiso=*) + # if an iso name is supplied, try to find the device where + # the iso resides on + set -- $(IFS==; echo $o) + isoPath=$2 + ;; + esac +done + +# Set hostid before modules are loaded. +# This is needed by the spl/zfs modules. +@setHostId@ + +# Load the required kernel modules. +echo @extraUtils@/bin/modprobe > /proc/sys/kernel/modprobe +for i in @kernelModules@; do + info "loading module $(basename $i)..." + modprobe $i +done + + +# Create device nodes in /dev. +@preDeviceCommands@ +info "running udev..." +ln -sfn /proc/self/fd /dev/fd +ln -sfn /proc/self/fd/0 /dev/stdin +ln -sfn /proc/self/fd/1 /dev/stdout +ln -sfn /proc/self/fd/2 /dev/stderr +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 +systemd-udevd --daemon +udevadm trigger --action=add +udevadm settle + + +# XXX: Use case usb->lvm will still fail, usb->luks->lvm is covered +@preLVMCommands@ + +info "starting device mapper and LVM..." +lvm vgchange -ay + +if test -n "$debug1devices"; then fail; fi + + +@postDeviceCommands@ + + +# Check the specified file system, if appropriate. +checkFS() { + local device="$1" + local fsType="$2" + + # Only check block devices. + if [ ! -b "$device" ]; then return 0; fi + + # Don't check ROM filesystems. + if [ "$fsType" = iso9660 -o "$fsType" = udf ]; then return 0; fi + + # Don't check resilient COWs as they validate the fs structures at mount time + if [ "$fsType" = btrfs -o "$fsType" = zfs -o "$fsType" = bcachefs ]; then return 0; fi + + # Skip fsck for apfs as the fsck utility does not support repairing the filesystem (no -a option) + if [ "$fsType" = apfs ]; then return 0; fi + + # Skip fsck for nilfs2 - not needed by design and no fsck tool for this filesystem. + if [ "$fsType" = nilfs2 ]; then return 0; fi + + # Skip fsck for inherently readonly filesystems. + if [ "$fsType" = squashfs ]; then return 0; fi + + # Skip fsck.erofs because it is still experimental. + if [ "$fsType" = erofs ]; then return 0; fi + + # If we couldn't figure out the FS type, then skip fsck. + if [ "$fsType" = auto ]; then + echo 'cannot check filesystem with type "auto"!' + return 0 + fi + + # 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" + return 0 + fi + + # Optionally, skip fsck on journaling filesystems. This option is + # a hack - it's mostly because e2fsck on ext3 takes much longer to + # recover the journal than the ext3 implementation in the kernel + # does (minutes versus seconds). + if test -z "@checkJournalingFS@" -a \ + \( "$fsType" = ext3 -o "$fsType" = ext4 -o "$fsType" = reiserfs \ + -o "$fsType" = xfs -o "$fsType" = jfs -o "$fsType" = f2fs \) + then + return 0 + fi + + echo "checking $device..." + + fsck -V -a "$device" + fsckResult=$? + + if test $(($fsckResult | 2)) = $fsckResult; then + echo "fsck finished, rebooting..." + sleep 3 + reboot -f + fi + + if test $(($fsckResult | 4)) = $fsckResult; then + echo "$device has unrepaired errors, please fix them manually." + fail + fi + + if test $fsckResult -ge 8; then + echo "fsck on $device failed." + fail + fi + + return 0 +} + +escapeFstab() { + local original="$1" + + # Replace space + local escaped="${original// /\\040}" + # Replace tab + echo "${escaped//$'\t'/\\011}" +} + +# Function for mounting a file system. +mountFS() { + local device="$1" + local mountPoint="$2" + local options="$3" + local fsType="$4" + + if [ "$fsType" = auto ]; then + fsType=$(blkid -o value -s TYPE "$device") + if [ -z "$fsType" ]; then fsType=auto; fi + fi + + # 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 $optionsPrefixed" >> /etc/fstab + + checkFS "$device" "$fsType" + + # 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 + + info "mounting $device on $mountPoint..." + + makeMountPoint "$device" "$mountPoint" "$optionsPrefixed" + + # For ZFS and CIFS mounts, retry a few times before giving up. + # We do this for ZFS as a workaround for issue NixOS/nixpkgs#25383. + local n=0 + while true; do + mount "/mnt-root$mountPoint" && break + if [ \( "$fsType" != cifs -a "$fsType" != zfs \) -o "$n" -ge 10 ]; then fail; break; fi + echo "retrying..." + sleep 1 + n=$((n + 1)) + done + + # For bind mounts, busybox has a tendency to ignore options, which can be a + # security issue (e.g. "nosuid"). Remounting the partition seems to fix the + # issue. + mount "/mnt-root$mountPoint" -o "remount,$optionsPrefixed" + + [ "$mountPoint" == "/" ] && + [ -f "/mnt-root/etc/NIXOS_LUSTRATE" ] && + lustrateRoot "/mnt-root" + + true +} + +lustrateRoot () { + local root="$1" + + echo + echo -e "\e[1;33m<<< @distroName@ is now lustrating the root filesystem (cruft goes to /old-root) >>>\e[0m" + echo + + mkdir -m 0755 -p "$root/old-root.tmp" + + echo + echo "Moving impurities out of the way:" + for d in "$root"/* + do + [ "$d" == "$root/nix" ] && continue + [ "$d" == "$root/boot" ] && continue # Don't render the system unbootable + [ "$d" == "$root/old-root.tmp" ] && continue + + mv -v "$d" "$root/old-root.tmp" + done + + # Use .tmp to make sure subsequent invocations don't clash + mv -v "$root/old-root.tmp" "$root/old-root" + + mkdir -m 0755 -p "$root/etc" + touch "$root/etc/NIXOS" + + exec 4< "$root/old-root/etc/NIXOS_LUSTRATE" + + echo + echo "Restoring selected impurities:" + while read -u 4 keeper; do + dirname="$(dirname "$keeper")" + mkdir -m 0755 -p "$root/$dirname" + cp -av "$root/old-root/$keeper" "$root/$keeper" + done + + exec 4>&- +} + + + +if test -e /sys/power/resume -a -e /sys/power/disk; then + if test -n "@resumeDevice@" && waitDevice "@resumeDevice@"; then + resumeDev="@resumeDevice@" + resumeInfo="$(udevadm info -q property "$resumeDev" )" + else + for sd in @resumeDevices@; do + # Try to detect resume device. According to Ubuntu bug: + # https://bugs.launchpad.net/ubuntu/+source/pm-utils/+bug/923326/comments/1 + # when there are multiple swap devices, we can't know where the hibernate + # image will reside. We can check all of them for swsuspend blkid. + if waitDevice "$sd"; then + resumeInfo="$(udevadm info -q property "$sd")" + if [ "$(echo "$resumeInfo" | sed -n 's/^ID_FS_TYPE=//p')" = "swsuspend" ]; then + resumeDev="$sd" + break + fi + fi + done + fi + if test -n "$resumeDev"; then + resumeMajor="$(echo "$resumeInfo" | sed -n 's/^MAJOR=//p')" + resumeMinor="$(echo "$resumeInfo" | sed -n 's/^MINOR=//p')" + echo "$resumeMajor:$resumeMinor" > /sys/power/resume 2> /dev/null || echo "failed to resume..." + fi +fi + +@postResumeCommands@ + +# If we have a path to an iso file, find the iso and link it to /dev/root +if [ -n "$isoPath" ]; then + mkdir -p /findiso + + for delay in 5 10; do + blkid | while read -r line; do + device=$(echo "$line" | sed 's/:.*//') + type=$(echo "$line" | sed 's/.*TYPE="\([^"]*\)".*/\1/') + + mount -t "$type" "$device" /findiso + if [ -e "/findiso$isoPath" ]; then + ln -sf "/findiso$isoPath" /dev/root + break 2 + else + umount /findiso + fi + done + + sleep "$delay" + done +fi + +# Try to find and mount the root device. +mkdir -p $targetRoot + +exec 3< @fsInfo@ + +while read -u 3 mountPoint; do + read -u 3 device + read -u 3 fsType + read -u 3 options + + # !!! Really quick hack to support bind mounts, i.e., where the + # "device" should be taken relative to /mnt-root, not /. Assume + # that every device that starts with / but doesn't start with /dev + # is a bind mount. + pseudoDevice= + case $device in + /dev/*) + ;; + //*) + # Don't touch SMB/CIFS paths. + pseudoDevice=1 + ;; + /*) + device=/mnt-root$device + ;; + *) + # Not an absolute path; assume that it's a pseudo-device + # like an NFS path (e.g. "server:/path"). + pseudoDevice=1 + ;; + esac + + if test -z "$pseudoDevice" && ! waitDevice "$device"; then + # If it doesn't appear, try to mount it anyway (and + # probably fail). This is a fallback for non-device "devices" + # that we don't properly recognise. + echo "Timed out waiting for device $device, trying to mount anyway." + fi + + # Wait once more for the udev queue to empty, just in case it's + # doing something with $device right now. + udevadm settle + + # If copytoram is enabled: skip mounting the ISO and copy its content to a tmpfs. + if [ -n "$copytoram" ] && [ "$device" = /dev/root ] && [ "$mountPoint" = /iso ]; then + fsType=$(blkid -o value -s TYPE "$device") + fsSize=$(blockdev --getsize64 "$device" || stat -Lc '%s' "$device") + + mkdir -p /tmp-iso + mount -t "$fsType" /dev/root /tmp-iso + mountFS tmpfs /iso size="$fsSize" tmpfs + + cp -r /tmp-iso/* /mnt-root/iso/ + + umount /tmp-iso + rmdir /tmp-iso + if [ -n "$isoPath" ] && [ $fsType = "iso9660" ] && mountpoint -q /findiso; then + umount /findiso + fi + continue + fi + + if [ "$mountPoint" = / ] && [ "$device" = tmpfs ] && [ ! -z "$persistence" ]; then + echo persistence... + waitDevice "$persistence" + echo enabling persistence... + mountFS "$persistence" "$mountPoint" "$persistence_opt" "auto" + continue + fi + + mountFS "$device" "$(escapeFstab "$mountPoint")" "$(escapeFstab "$options")" "$fsType" +done + +exec 3>&- + + +@postMountCommands@ + + +# Emit a udev rule for /dev/root to prevent systemd from complaining. +if [ -e /mnt-root/iso ]; then + eval $(udevadm info --export --export-prefix=ROOT_ --device-id-of-file=/mnt-root/iso) +else + eval $(udevadm info --export --export-prefix=ROOT_ --device-id-of-file=$targetRoot) +fi +if [ "$ROOT_MAJOR" -a "$ROOT_MINOR" -a "$ROOT_MAJOR" != 0 ]; then + mkdir -p /run/udev/rules.d + echo 'ACTION=="add|change", SUBSYSTEM=="block", ENV{MAJOR}=="'$ROOT_MAJOR'", ENV{MINOR}=="'$ROOT_MINOR'", SYMLINK+="root"' > /run/udev/rules.d/61-dev-root-link.rules +fi + + +# Stop udevd. +udevadm control --exit + +# Reset the logging file descriptors. +# Do this just before pkill, which will kill the tee process. +exec 1>&$logOutFd 2>&$logErrFd +eval "exec $logOutFd>&- $logErrFd>&-" + +# Kill any remaining processes, just to be sure we're not taking any +# with us into stage 2. But keep storage daemons like unionfs-fuse. +# +# Storage daemons are distinguished by an @ in front of their command line: +# https://www.freedesktop.org/wiki/Software/systemd/RootStorageDaemons/ +for pid in $(pgrep -v -f '^@'); do + # Make sure we don't kill kernel processes, see #15226 and: + # http://stackoverflow.com/questions/12213445/identifying-kernel-threads + readlink "/proc/$pid/exe" &> /dev/null || continue + # Try to avoid killing ourselves. + [ $pid -eq $$ ] && continue + kill -9 "$pid" +done + +if test -n "$debug1mounts"; then fail; fi + + +# Restore /proc/sys/kernel/modprobe to its original value. +echo /sbin/modprobe > /proc/sys/kernel/modprobe + + +# Start stage 2. `switch_root' deletes all files in the ramfs on the +# current root. The path has to be valid in the chroot not outside. +if [ ! -e "$targetRoot/$stage2Init" ]; then + stage2Check=${stage2Init} + while [ "$stage2Check" != "${stage2Check%/*}" ] && [ ! -L "$targetRoot/$stage2Check" ]; do + stage2Check=${stage2Check%/*} + done + if [ ! -L "$targetRoot/$stage2Check" ]; then + echo "stage 2 init script ($targetRoot/$stage2Init) not found" + fail + fi +fi + +mkdir -m 0755 -p $targetRoot/proc $targetRoot/sys $targetRoot/dev $targetRoot/run + +mount --move /proc $targetRoot/proc +mount --move /sys $targetRoot/sys +mount --move /dev $targetRoot/dev +mount --move /run $targetRoot/run + +exec env -i $(type -P switch_root) "$targetRoot" "$stage2Init" + +fail # should never be reached diff --git a/nixpkgs/nixos/modules/system/boot/stage-1.nix b/nixpkgs/nixos/modules/system/boot/stage-1.nix new file mode 100644 index 000000000000..e990aeea7a14 --- /dev/null +++ b/nixpkgs/nixos/modules/system/boot/stage-1.nix @@ -0,0 +1,735 @@ +# This module builds the initial ramdisk, which contains an init +# script that performs the first stage of booting the system: it loads +# the modules necessary to mount the root file system, then calls the +# init in the root file system to start the second boot stage. + +{ config, lib, utils, pkgs, ... }: + +with lib; + +let + + udev = config.systemd.package; + + kernel-name = config.boot.kernelPackages.kernel.name or "kernel"; + + modulesTree = config.system.modulesTree.override { name = kernel-name + "-modules"; }; + firmware = config.hardware.firmware; + + + # Determine the set of modules that we need to mount the root FS. + modulesClosure = pkgs.makeModulesClosure { + rootModules = config.boot.initrd.availableKernelModules ++ config.boot.initrd.kernelModules; + kernel = modulesTree; + firmware = firmware; + allowMissing = false; + }; + + + # The initrd only has to mount `/` or any FS marked as necessary for + # booting (such as the FS containing `/nix/store`, or an FS needed for + # mounting `/`, like `/` on a loopback). + fileSystems = filter utils.fsNeededForBoot config.system.build.fileSystems; + + # Determine whether zfs-mount(8) is needed. + zfsRequiresMountHelper = any (fs: lib.elem "zfsutil" fs.options) fileSystems; + + # A utility for enumerating the shared-library dependencies of a program + findLibs = pkgs.buildPackages.writeShellScriptBin "find-libs" '' + set -euo pipefail + + declare -A seen + left=() + + patchelf="${pkgs.buildPackages.patchelf}/bin/patchelf" + + function add_needed { + rpath="$($patchelf --print-rpath $1)" + dir="$(dirname $1)" + for lib in $($patchelf --print-needed $1); do + left+=("$lib" "$rpath" "$dir") + done + } + + add_needed "$1" + + while [ ''${#left[@]} -ne 0 ]; do + next=''${left[0]} + rpath=''${left[1]} + ORIGIN=''${left[2]} + left=("''${left[@]:3}") + if [ -z ''${seen[$next]+x} ]; then + seen[$next]=1 + + # Ignore the dynamic linker which for some reason appears as a DT_NEEDED of glibc but isn't in glibc's RPATH. + case "$next" in + ld*.so.?) continue;; + esac + + IFS=: read -ra paths <<< $rpath + res= + for path in "''${paths[@]}"; do + path=$(eval "echo $path") + if [ -f "$path/$next" ]; then + res="$path/$next" + echo "$res" + add_needed "$res" + break + fi + done + if [ -z "$res" ]; then + echo "Couldn't satisfy dependency $next" >&2 + exit 1 + fi + fi + done + ''; + + # Some additional utilities needed in stage 1, like mount, lvm, fsck + # etc. We don't want to bring in all of those packages, so we just + # copy what we need. Instead of using statically linked binaries, + # we just copy what we need from Glibc and use patchelf to make it + # work. + extraUtils = pkgs.runCommand "extra-utils" + { nativeBuildInputs = with pkgs.buildPackages; [ nukeReferences bintools ]; + allowedReferences = [ "out" ]; # prevent accidents like glibc being included in the initrd + } + '' + set +o pipefail + + mkdir -p $out/bin $out/lib + ln -s $out/bin $out/sbin + + copy_bin_and_libs () { + [ -f "$out/bin/$(basename $1)" ] && rm "$out/bin/$(basename $1)" + cp -pdv $1 $out/bin + } + + # Copy BusyBox. + for BIN in ${pkgs.busybox}/{s,}bin/*; do + copy_bin_and_libs $BIN + done + + ${optionalString zfsRequiresMountHelper '' + # Filesystems using the "zfsutil" option are mounted regardless of the + # mount.zfs(8) helper, but it is required to ensure that ZFS properties + # are used as mount options. + # + # BusyBox does not use the ZFS helper in the first place. + # util-linux searches /sbin/ as last path for helpers (stage-1-init.sh + # must symlink it to the store PATH). + # Without helper program, both `mount`s silently fails back to internal + # code, using default options and effectively ignore security relevant + # ZFS properties such as `setuid=off` and `exec=off` (unless manually + # duplicated in `fileSystems.*.options`, defeating "zfsutil"'s purpose). + copy_bin_and_libs ${lib.getOutput "mount" pkgs.util-linux}/bin/mount + copy_bin_and_libs ${config.boot.zfs.package}/bin/mount.zfs + ''} + + # Copy some util-linux stuff. + copy_bin_and_libs ${pkgs.util-linux}/sbin/blkid + + # Copy dmsetup and lvm. + copy_bin_and_libs ${getBin pkgs.lvm2}/bin/dmsetup + copy_bin_and_libs ${getBin pkgs.lvm2}/bin/lvm + + # Copy udev. + copy_bin_and_libs ${udev}/bin/udevadm + copy_bin_and_libs ${udev}/lib/systemd/systemd-sysctl + for BIN in ${udev}/lib/udev/*_id; do + copy_bin_and_libs $BIN + done + # systemd-udevd is only a symlink to udevadm these days + ln -sf udevadm $out/bin/systemd-udevd + + # Copy modprobe. + copy_bin_and_libs ${pkgs.kmod}/bin/kmod + ln -sf kmod $out/bin/modprobe + + # Copy multipath. + ${optionalString config.services.multipath.enable '' + copy_bin_and_libs ${config.services.multipath.package}/bin/multipath + copy_bin_and_libs ${config.services.multipath.package}/bin/multipathd + # Copy lib/multipath manually. + cp -rpv ${config.services.multipath.package}/lib/multipath $out/lib + ''} + + # 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}") + # 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)) + } + + ${config.boot.initrd.extraUtilsCommands} + + # Copy ld manually since it isn't detected correctly + cp -pv ${pkgs.stdenv.cc.libc.out}/lib/ld*.so.? $out/lib + + # Copy all of the needed libraries in a consistent order so + # duplicates are resolved the same way. + find $out/bin $out/lib -type f | sort | while read BIN; do + echo "Copying libs for executable $BIN" + for LIB in $(${findLibs}/bin/find-libs $BIN); do + TGT="$out/lib/$(basename $LIB)" + if [ ! -f "$TGT" ]; then + SRC="$(readlink -e $LIB)" + cp -pdv "$SRC" "$TGT" + fi + done + done + + # Strip binaries further than normal. + chmod -R u+w $out + stripDirs "$STRIP" "$RANLIB" "lib bin" "-s" + + # Run patchelf to make the programs refer to the copied libraries. + find $out/bin $out/lib -type f | while read i; do + nuke-refs -e $out $i + done + + find $out/bin -type f | while read i; do + echo "patching $i..." + patchelf --set-interpreter $out/lib/ld*.so.? --set-rpath $out/lib $i || true + done + + find $out/lib -type f \! -name 'ld*.so.?' | while read i; do + echo "patching $i..." + patchelf --set-rpath $out/lib $i + done + + if [ -z "${toString (pkgs.stdenv.hostPlatform != pkgs.stdenv.buildPlatform)}" ]; then + # Make sure that the patchelf'ed binaries still work. + echo "testing patched programs..." + $out/bin/ash -c 'echo hello world' | grep "hello world" + ${if zfsRequiresMountHelper then '' + $out/bin/mount -V 1>&1 | grep -q "mount from util-linux" + $out/bin/mount.zfs -h 2>&1 | grep -q "Usage: mount.zfs" + '' else '' + $out/bin/mount --help 2>&1 | grep -q "BusyBox" + ''} + $out/bin/blkid -V 2>&1 | grep -q 'libblkid' + $out/bin/udevadm --version + $out/bin/dmsetup --version 2>&1 | tee -a log | grep -q "version:" + LVM_SYSTEM_DIR=$out $out/bin/lvm version 2>&1 | tee -a log | grep -q "LVM" + ${optionalString config.services.multipath.enable '' + ($out/bin/multipath || true) 2>&1 | grep -q 'need to be root' + ($out/bin/multipathd || true) 2>&1 | grep -q 'need to be root' + ''} + + ${config.boot.initrd.extraUtilsCommandsTest} + fi + ''; # */ + + + # Networkd link files are used early by udev to set up interfaces early. + # This must be done in stage 1 to avoid race conditions between udev and + # network daemons. + linkUnits = pkgs.runCommand "link-units" { + allowedReferences = [ extraUtils ]; + preferLocalBuild = true; + } ('' + mkdir -p $out + cp -v ${udev}/lib/systemd/network/*.link $out/ + '' + ( + let + links = filterAttrs (n: v: hasSuffix ".link" n) config.systemd.network.units; + files = mapAttrsToList (n: v: "${v.unit}/${n}") links; + in + concatMapStringsSep "\n" (file: "cp -v ${file} $out/") files + )); + + udevRules = pkgs.runCommand "udev-rules" { + allowedReferences = [ extraUtils ]; + preferLocalBuild = true; + } '' + mkdir -p $out + + 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} + + for i in $out/*.rules; do + substituteInPlace $i \ + --replace ata_id ${extraUtils}/bin/ata_id \ + --replace scsi_id ${extraUtils}/bin/scsi_id \ + --replace cdrom_id ${extraUtils}/bin/cdrom_id \ + --replace ${pkgs.coreutils}/bin/basename ${extraUtils}/bin/basename \ + --replace ${pkgs.util-linux}/bin/blkid ${extraUtils}/bin/blkid \ + --replace ${getBin pkgs.lvm2}/bin ${extraUtils}/bin \ + --replace ${pkgs.mdadm}/sbin ${extraUtils}/sbin \ + --replace ${pkgs.bash}/bin/sh ${extraUtils}/bin/sh \ + --replace ${udev} ${extraUtils} + done + + # Work around a bug in QEMU, which doesn't implement the "READ + # DISC INFORMATION" SCSI command: + # https://bugzilla.redhat.com/show_bug.cgi?id=609049 + # As a result, `cdrom_id' doesn't print + # ID_CDROM_MEDIA_TRACK_COUNT_DATA, which in turn prevents the + # /dev/disk/by-label symlinks from being created. We need these + # in the NixOS installation CD, so use ID_CDROM_MEDIA in the + # corresponding udev rules for now. This was the behaviour in + # udev <= 154. See also + # https://www.spinics.net/lists/hotplug/msg03935.html + substituteInPlace $out/60-persistent-storage.rules \ + --replace ID_CDROM_MEDIA_TRACK_COUNT_DATA ID_CDROM_MEDIA + ''; # */ + + + # The init script of boot stage 1 (loading kernel modules for + # mounting the root FS). + bootStage1 = pkgs.substituteAll { + src = ./stage-1-init.sh; + + shell = "${extraUtils}/bin/ash"; + + isExecutable = true; + + postInstall = '' + echo checking syntax + # check both with bash + ${pkgs.buildPackages.bash}/bin/sh -n $target + # and with ash shell, just in case + ${pkgs.buildPackages.busybox}/bin/ash -n $target + ''; + + inherit linkUnits udevRules extraUtils; + + inherit (config.boot) resumeDevice; + + inherit (config.system.nixos) distroName; + + inherit (config.system.build) earlyMountScript; + + inherit (config.boot.initrd) checkJournalingFS verbose + preLVMCommands preDeviceCommands postDeviceCommands postResumeCommands postMountCommands preFailCommands kernelModules; + + resumeDevices = map (sd: if sd ? device then sd.device else "/dev/disk/by-label/${sd.label}") + (filter (sd: hasPrefix "/dev/" sd.device && !sd.randomEncryption.enable + # Don't include zram devices + && !(hasPrefix "/dev/zram" sd.device) + ) config.swapDevices); + + fsInfo = + let f = fs: [ fs.mountPoint (if fs.device != null then fs.device else "/dev/disk/by-label/${fs.label}") fs.fsType (builtins.concatStringsSep "," fs.options) ]; + in pkgs.writeText "initrd-fsinfo" (concatStringsSep "\n" (concatMap f fileSystems)); + + setHostId = optionalString (config.networking.hostId != null) '' + hi="${config.networking.hostId}" + ${if pkgs.stdenv.isBigEndian then '' + echo -ne "\x''${hi:0:2}\x''${hi:2:2}\x''${hi:4:2}\x''${hi:6:2}" > /etc/hostid + '' else '' + echo -ne "\x''${hi:6:2}\x''${hi:4:2}\x''${hi:2:2}\x''${hi:0:2}" > /etc/hostid + ''} + ''; + }; + + + # The closure of the init script of boot stage 1 is what we put in + # the initial RAM disk. + initialRamdisk = pkgs.makeInitrd { + name = "initrd-${kernel-name}"; + inherit (config.boot.initrd) compressor compressorArgs prepend; + + contents = + [ { object = bootStage1; + symlink = "/init"; + } + { object = "${modulesClosure}/lib"; + symlink = "/lib"; + } + { object = pkgs.runCommand "initrd-kmod-blacklist-ubuntu" { + src = "${pkgs.kmod-blacklist-ubuntu}/modprobe.conf"; + preferLocalBuild = true; + } '' + target=$out + ${pkgs.buildPackages.perl}/bin/perl -0pe 's/## file: iwlwifi.conf(.+?)##/##/s;' $src > $out + ''; + symlink = "/etc/modprobe.d/ubuntu.conf"; + } + { object = config.environment.etc."modprobe.d/nixos.conf".source; + symlink = "/etc/modprobe.d/nixos.conf"; + } + { object = pkgs.kmod-debian-aliases; + symlink = "/etc/modprobe.d/debian.conf"; + } + ] ++ lib.optionals config.services.multipath.enable [ + { object = pkgs.runCommand "multipath.conf" { + src = config.environment.etc."multipath.conf".text; + preferLocalBuild = true; + } '' + target=$out + printf "$src" > $out + substituteInPlace $out \ + --replace ${config.services.multipath.package}/lib ${extraUtils}/lib + ''; + symlink = "/etc/multipath.conf"; + } + ] ++ (lib.mapAttrsToList + (symlink: options: + { + inherit symlink; + object = options.source; + } + ) + config.boot.initrd.extraFiles); + }; + + # Script to add secret files to the initrd at bootloader update time + initialRamdiskSecretAppender = + let + compressorExe = initialRamdisk.compressorExecutableFunction pkgs; + in pkgs.writeScriptBin "append-initrd-secrets" + '' + #!${pkgs.bash}/bin/bash -e + function usage { + echo "USAGE: $0 INITRD_FILE" >&2 + echo "Appends this configuration's secrets to INITRD_FILE" >&2 + } + + if [ $# -ne 1 ]; then + usage + exit 1 + fi + + if [ "$1"x = "--helpx" ]; then + usage + exit 0 + fi + + ${lib.optionalString (config.boot.initrd.secrets == {}) + "exit 0"} + + export PATH=${pkgs.coreutils}/bin:${pkgs.libarchive}/bin:${pkgs.gzip}/bin:${pkgs.findutils}/bin + + function cleanup { + if [ -n "$tmp" -a -d "$tmp" ]; then + rm -fR "$tmp" + fi + } + trap cleanup EXIT + + tmp=$(mktemp -d ''${TMPDIR:-/tmp}/initrd-secrets.XXXXXXXXXX) + + ${lib.concatStringsSep "\n" (mapAttrsToList (dest: source: + let source' = if source == null then dest else toString source; in + '' + mkdir -p $(dirname "$tmp/.initrd-secrets/${dest}") + cp -a ${source'} "$tmp/.initrd-secrets/${dest}" + '' + ) config.boot.initrd.secrets) + } + + # mindepth 1 so that we don't change the mode of / + (cd "$tmp" && find . -mindepth 1 | xargs touch -amt 197001010000 && find . -mindepth 1 -print0 | sort -z | bsdtar --uid 0 --gid 0 -cnf - -T - | bsdtar --null -cf - --format=newc @-) | \ + ${compressorExe} ${lib.escapeShellArgs initialRamdisk.compressorArgs} >> "$1" + ''; + +in + +{ + options = { + + boot.resumeDevice = mkOption { + type = types.str; + default = ""; + example = "/dev/sda3"; + description = lib.mdDoc '' + Device for manual resume attempt during boot. This should be used primarily + if you want to resume from file. If left empty, the swap partitions are used. + Specify here the device where the file resides. + You should also use {var}`boot.kernelParams` to specify + `«resume_offset»`. + ''; + }; + + boot.initrd.enable = mkOption { + type = types.bool; + default = !config.boot.isContainer; + defaultText = literalExpression "!config.boot.isContainer"; + description = lib.mdDoc '' + 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.extraFiles = mkOption { + default = { }; + type = types.attrsOf + (types.submodule { + options = { + source = mkOption { + type = types.package; + description = lib.mdDoc "The object to make available inside the initrd."; + }; + }; + }); + description = lib.mdDoc '' + Extra files to link and copy in to the initrd. + ''; + }; + + boot.initrd.prepend = mkOption { + default = [ ]; + type = types.listOf types.str; + description = lib.mdDoc '' + Other initrd files to prepend to the final initrd we are building. + ''; + }; + + boot.initrd.checkJournalingFS = mkOption { + default = true; + type = types.bool; + description = lib.mdDoc '' + Whether to run {command}`fsck` on journaling filesystems such as ext3. + ''; + }; + + boot.initrd.preLVMCommands = mkOption { + default = ""; + type = types.lines; + description = lib.mdDoc '' + Shell commands to be executed immediately before LVM discovery. + ''; + }; + + boot.initrd.preDeviceCommands = mkOption { + default = ""; + type = types.lines; + description = lib.mdDoc '' + Shell commands to be executed before udev is started to create + device nodes. + ''; + }; + + boot.initrd.postDeviceCommands = mkOption { + default = ""; + type = types.lines; + description = lib.mdDoc '' + Shell commands to be executed immediately after stage 1 of the + boot has loaded kernel modules and created device nodes in + {file}`/dev`. + ''; + }; + + boot.initrd.postResumeCommands = mkOption { + default = ""; + type = types.lines; + description = lib.mdDoc '' + Shell commands to be executed immediately after attempting to resume. + ''; + }; + + boot.initrd.postMountCommands = mkOption { + default = ""; + type = types.lines; + description = lib.mdDoc '' + Shell commands to be executed immediately after the stage 1 + filesystems have been mounted. + ''; + }; + + boot.initrd.preFailCommands = mkOption { + default = ""; + type = types.lines; + description = lib.mdDoc '' + Shell commands to be executed before the failure prompt is shown. + ''; + }; + + boot.initrd.extraUtilsCommands = mkOption { + internal = true; + default = ""; + type = types.lines; + description = lib.mdDoc '' + Shell commands to be executed in the builder of the + extra-utils derivation. This can be used to provide + additional utilities in the initial ramdisk. + ''; + }; + + boot.initrd.extraUtilsCommandsTest = mkOption { + internal = true; + default = ""; + type = types.lines; + description = lib.mdDoc '' + Shell commands to be executed in the builder of the + extra-utils derivation after patchelf has done its + job. This can be used to test additional utilities + copied in extraUtilsCommands. + ''; + }; + + boot.initrd.extraUdevRulesCommands = mkOption { + internal = true; + default = ""; + type = types.lines; + description = lib.mdDoc '' + Shell commands to be executed in the builder of the + udev-rules derivation. This can be used to add + additional udev rules in the initial ramdisk. + ''; + }; + + boot.initrd.compressor = mkOption { + default = ( + if lib.versionAtLeast config.boot.kernelPackages.kernel.version "5.9" + then "zstd" + else "gzip" + ); + defaultText = literalMD "`zstd` if the kernel supports it (5.9+), `gzip` if not"; + type = types.either types.str (types.functionTo types.str); + description = lib.mdDoc '' + The compressor to use on the initrd image. May be any of: + + - The name of one of the predefined compressors, see {file}`pkgs/build-support/kernel/initrd-compressor-meta.nix` for the definitions. + - A function which, given the nixpkgs package set, returns the path to a compressor tool, e.g. `pkgs: "''${pkgs.pigz}/bin/pigz"` + - (not recommended, because it does not work when cross-compiling) the full path to a compressor tool, e.g. `"''${pkgs.pigz}/bin/pigz"` + + The given program should read data from stdin and write it to stdout compressed. + ''; + example = "xz"; + }; + + boot.initrd.compressorArgs = mkOption { + default = null; + type = types.nullOr (types.listOf types.str); + description = lib.mdDoc "Arguments to pass to the compressor for the initrd image, or null to use the compressor's defaults."; + }; + + boot.initrd.secrets = mkOption + { default = {}; + type = types.attrsOf (types.nullOr types.path); + description = + lib.mdDoc '' + Secrets to append to the initrd. The attribute name is the + path the secret should have inside the initrd, the value + is the path it should be copied from (or null for the same + path inside and out). + ''; + example = literalExpression + '' + { "/etc/dropbear/dropbear_rsa_host_key" = + ./secret-dropbear-key; + } + ''; + }; + + boot.initrd.supportedFilesystems = mkOption { + default = [ ]; + example = [ "btrfs" ]; + type = types.listOf types.str; + description = lib.mdDoc "Names of supported filesystem types in the initial ramdisk."; + }; + + boot.initrd.verbose = mkOption { + default = true; + type = types.bool; + description = + lib.mdDoc '' + Verbosity of the initrd. Please note that disabling verbosity removes + only the mandatory messages generated by the NixOS scripts. For a + completely silent boot, you might also want to set the two following + configuration options: + + - `boot.consoleLogLevel = 0;` + - `boot.kernelParams = [ "quiet" "udev.log_level=3" ];` + ''; + }; + + boot.loader.supportsInitrdSecrets = mkOption + { internal = true; + default = false; + type = types.bool; + description = + lib.mdDoc '' + Whether the bootloader setup runs append-initrd-secrets. + If not, any needed secrets must be copied into the initrd + and thus added to the store. + ''; + }; + + fileSystems = mkOption { + type = with lib.types; attrsOf (submodule { + options.neededForBoot = mkOption { + default = false; + type = types.bool; + description = lib.mdDoc '' + If set, this file system will be mounted in the initial ramdisk. + Note that the file system will always be mounted in the initial + ramdisk if its mount point is one of the following: + ${concatStringsSep ", " ( + forEach utils.pathsNeededForBoot (i: "{file}`${i}`") + )}. + ''; + }; + }); + }; + + }; + + config = mkIf config.boot.initrd.enable { + assertions = [ + { assertion = any (fs: fs.mountPoint == "/") fileSystems; + message = "The ‘fileSystems’ option does not specify your root file system."; + } + { assertion = let inherit (config.boot) resumeDevice; in + resumeDevice == "" || builtins.substring 0 1 resumeDevice == "/"; + 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 builtins.storeDir source)) + (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 = mkMerge [ + { inherit bootStage1 initialRamdiskSecretAppender extraUtils; } + + # generated in nixos/modules/system/boot/systemd/initrd.nix + (mkIf (!config.boot.initrd.systemd.enable) { inherit initialRamdisk; }) + ]; + + system.requiredKernelConfig = with config.lib.kernelConfig; [ + (isYes "TMPFS") + (isYes "BLK_DEV_INITRD") + ]; + + boot.initrd.supportedFilesystems = map (fs: fs.fsType) fileSystems; + }; + + imports = [ + (mkRenamedOptionModule [ "boot" "initrd" "mdadmConf" ] [ "boot" "swraid" "mdadmConf" ]) + ]; +} diff --git a/nixpkgs/nixos/modules/system/boot/stage-2-init.sh b/nixpkgs/nixos/modules/system/boot/stage-2-init.sh new file mode 100755 index 000000000000..a89e3d817637 --- /dev/null +++ b/nixpkgs/nixos/modules/system/boot/stage-2-init.sh @@ -0,0 +1,147 @@ +#! @shell@ + +systemConfig=@systemConfig@ + +export HOME=/root PATH="@path@" + + +if [ "${IN_NIXOS_SYSTEMD_STAGE1:-}" != true ]; then + # Process the kernel command line. + for o in $(</proc/cmdline); do + case $o in + boot.debugtrace) + # Show each command. + set -x + ;; + esac + done + + + # Print a greeting. + echo + echo -e "\e[1;32m<<< @distroName@ Stage 2 >>>\e[0m" + echo + + + # Normally, stage 1 mounts the root filesystem read/writable. + # However, in some environments, stage 2 is executed directly, and the + # root is read-only. So make it writable here. + if [ -z "$container" ]; then + mount -n -o remount,rw none / + fi +fi + + +# Likewise, stage 1 mounts /proc, /dev and /sys, so if we don't have a +# stage 1, we need to do that here. +if [ ! -e /proc/1 ]; then + specialMount() { + local device="$1" + local mountPoint="$2" + local options="$3" + local fsType="$4" + + # We must not overwrite this mount because it's bind-mounted + # from stage 1's /run + if [ "${IN_NIXOS_SYSTEMD_STAGE1:-}" = true ] && [ "${mountPoint}" = /run ]; then + return + fi + + install -m 0755 -d "$mountPoint" + mount -n -t "$fsType" -o "$options" "$device" "$mountPoint" + } + source @earlyMountScript@ +fi + + +if [ "${IN_NIXOS_SYSTEMD_STAGE1:-}" = true ] || [ ! -c /dev/kmsg ] ; then + echo "booting system configuration ${systemConfig}" +else + echo "booting system configuration $systemConfig" > /dev/kmsg +fi + + +# Make /nix/store a read-only bind mount to enforce immutability of +# the Nix store. Note that we can't use "chown root:nixbld" here +# because users/groups might not exist yet. +# Silence chown/chmod to fail gracefully on a readonly filesystem +# like squashfs. +chown -f 0:30000 /nix/store +chmod -f 1775 /nix/store +if [ -n "@readOnlyNixStore@" ]; then + if ! [[ "$(findmnt --noheadings --output OPTIONS /nix/store)" =~ ro(,|$) ]]; then + if [ -z "$container" ]; then + mount --bind /nix/store /nix/store + else + mount --rbind /nix/store /nix/store + fi + mount -o remount,ro,bind /nix/store + fi +fi + + +if [ "${IN_NIXOS_SYSTEMD_STAGE1:-}" != true ]; then + # Use /etc/resolv.conf supplied by systemd-nspawn, if applicable. + if [ -n "@useHostResolvConf@" ] && [ -e /etc/resolv.conf ]; then + resolvconf -m 1000 -a host </etc/resolv.conf + fi + + + # Log the script output to /dev/kmsg or /run/log/stage-2-init.log. + # Only at this point are all the necessary prerequisites ready for these commands. + exec {logOutFd}>&1 {logErrFd}>&2 + if test -w /dev/kmsg; then + exec > >(tee -i /proc/self/fd/"$logOutFd" | while read -r line; do + if test -n "$line"; then + echo "<7>stage-2-init: $line" > /dev/kmsg + fi + done) 2>&1 + else + mkdir -p /run/log + exec > >(tee -i /run/log/stage-2-init.log) 2>&1 + fi +fi + + +# Required by the activation script +install -m 0755 -d /etc +if [ ! -h "/etc/nixos" ]; then + install -m 0755 -d /etc/nixos +fi +install -m 01777 -d /tmp + + +# Run the script that performs all configuration activation that does +# not have to be done at boot time. +echo "running activation script..." +$systemConfig/activate + + +# Record the boot configuration. +ln -sfn "$systemConfig" /run/booted-system + + +# Run any user-specified commands. +@shell@ @postBootCommands@ + + +# Ensure systemd doesn't try to populate /etc, by forcing its first-boot +# heuristic off. It doesn't matter what's in /etc/machine-id for this purpose, +# and systemd will immediately fill in the file when it starts, so just +# creating it is enough. This `: >>` pattern avoids forking and avoids changing +# the mtime if the file already exists. +: >> /etc/machine-id + + +# No need to restore the stdout/stderr streams we never redirected and +# especially no need to start systemd +if [ "${IN_NIXOS_SYSTEMD_STAGE1:-}" != true ]; then + # Reset the logging file descriptors. + exec 1>&$logOutFd 2>&$logErrFd + exec {logOutFd}>&- {logErrFd}>&- + + + # Start systemd in a clean environment. + echo "starting systemd..." + exec @systemdExecutable@ "$@" +fi diff --git a/nixpkgs/nixos/modules/system/boot/stage-2.nix b/nixpkgs/nixos/modules/system/boot/stage-2.nix new file mode 100644 index 000000000000..001380158d5f --- /dev/null +++ b/nixpkgs/nixos/modules/system/boot/stage-2.nix @@ -0,0 +1,82 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + + useHostResolvConf = config.networking.resolvconf.enable && config.networking.useHostResolvConf; + + bootStage2 = pkgs.substituteAll { + src = ./stage-2-init.sh; + shellDebug = "${pkgs.bashInteractive}/bin/bash"; + shell = "${pkgs.bash}/bin/bash"; + inherit (config.boot) readOnlyNixStore systemdExecutable extraSystemdUnitPaths; + inherit (config.system.nixos) distroName; + isExecutable = true; + inherit useHostResolvConf; + inherit (config.system.build) earlyMountScript; + path = lib.makeBinPath ([ + pkgs.coreutils + pkgs.util-linux + ] ++ lib.optional useHostResolvConf pkgs.openresolv); + postBootCommands = pkgs.writeText "local-cmds" + '' + ${config.boot.postBootCommands} + ${config.powerManagement.powerUpCommands} + ''; + }; + +in + +{ + options = { + + boot = { + + postBootCommands = mkOption { + default = ""; + example = "rm -f /var/log/messages"; + type = types.lines; + description = lib.mdDoc '' + Shell commands to be executed just before systemd is started. + ''; + }; + + readOnlyNixStore = mkOption { + type = types.bool; + default = true; + description = lib.mdDoc '' + If set, NixOS will enforce the immutability of the Nix store + by making {file}`/nix/store` a read-only bind + mount. Nix will automatically make the store writable when + needed. + ''; + }; + + systemdExecutable = mkOption { + default = "/run/current-system/systemd/lib/systemd/systemd"; + type = types.str; + description = lib.mdDoc '' + The program to execute to start systemd. + ''; + }; + + extraSystemdUnitPaths = mkOption { + default = []; + type = types.listOf types.str; + description = lib.mdDoc '' + Additional paths that get appended to the SYSTEMD_UNIT_PATH environment variable + that can contain mutable unit files. + ''; + }; + }; + + }; + + + config = { + + system.build.bootStage2 = bootStage2; + + }; +} diff --git a/nixpkgs/nixos/modules/system/boot/stratisroot.nix b/nixpkgs/nixos/modules/system/boot/stratisroot.nix new file mode 100644 index 000000000000..241d044db2fe --- /dev/null +++ b/nixpkgs/nixos/modules/system/boot/stratisroot.nix @@ -0,0 +1,64 @@ +{ config, lib, pkgs, utils, ... }: +let + requiredStratisFilesystems = lib.attrsets.filterAttrs (_: x: utils.fsNeededForBoot x && x.stratis.poolUuid != null) config.fileSystems; +in +{ + options = {}; + config = lib.mkIf (requiredStratisFilesystems != {}) { + assertions = [ + { + assertion = config.boot.initrd.systemd.enable; + message = "stratis root fs requires systemd stage 1"; + } + ]; + boot.initrd = { + systemd = { + storePaths = [ + "${pkgs.stratisd}/lib/udev/stratis-base32-decode" + "${pkgs.stratisd}/lib/udev/stratis-str-cmp" + "${pkgs.lvm2.bin}/bin/dmsetup" + "${pkgs.stratisd}/libexec/stratisd-min" + "${pkgs.stratisd.initrd}/bin/stratis-rootfs-setup" + ]; + packages = [pkgs.stratisd.initrd]; + extraBin = { + thin_check = "${pkgs."thin-provisioning-tools"}/bin/thin_check"; + thin_repair = "${pkgs."thin-provisioning-tools"}/bin/thin_repair"; + thin_metadata_size = "${pkgs."thin-provisioning-tools"}/bin/thin_metadata_size"; + stratis-min = "${pkgs.stratisd}/bin/stratis-min"; + }; + services = + lib.attrsets.mapAttrs' ( + mountPoint: fileSystem: { + name = "stratis-setup-${fileSystem.stratis.poolUuid}"; + value = { + description = "setup for Stratis root filesystem"; + unitConfig.DefaultDependencies = "no"; + conflicts = [ "shutdown.target" "initrd-switch-root.target" ]; + onFailure = [ "emergency.target" ]; + unitConfig.OnFailureJobMode = "isolate"; + wants = [ "stratisd-min.service" "plymouth-start.service" ]; + wantedBy = [ "initrd.target" ]; + after = [ "paths.target" "plymouth-start.service" "stratisd-min.service" ]; + before = [ "initrd.target" "shutdown.target" "initrd-switch-root.target" ]; + environment.STRATIS_ROOTFS_UUID = fileSystem.stratis.poolUuid; + serviceConfig = { + Type = "oneshot"; + ExecStart = "${pkgs.stratisd.initrd}/bin/stratis-rootfs-setup"; + RemainAfterExit = "yes"; + }; + }; + } + ) requiredStratisFilesystems; + }; + availableKernelModules = [ "dm-thin-pool" "dm-crypt" ] ++ [ "aes" "aes_generic" "blowfish" "twofish" + "serpent" "cbc" "xts" "lrw" "sha1" "sha256" "sha512" + "af_alg" "algif_skcipher" + ]; + services.udev.packages = [ + pkgs.stratisd.initrd + pkgs.lvm2 + ]; + }; + }; +} diff --git a/nixpkgs/nixos/modules/system/boot/systemd.nix b/nixpkgs/nixos/modules/system/boot/systemd.nix new file mode 100644 index 000000000000..e29fa49ea23b --- /dev/null +++ b/nixpkgs/nixos/modules/system/boot/systemd.nix @@ -0,0 +1,706 @@ +{ config, lib, pkgs, utils, ... }: + +with utils; +with systemdUtils.unitOptions; +with lib; + +let + + cfg = config.systemd; + + inherit (systemdUtils.lib) + generateUnits + targetToUnit + serviceToUnit + socketToUnit + timerToUnit + pathToUnit + mountToUnit + automountToUnit + sliceToUnit; + + upstreamSystemUnits = + [ # Targets. + "basic.target" + "sysinit.target" + "sockets.target" + "exit.target" + "graphical.target" + "multi-user.target" + "network.target" + "network-pre.target" + "network-online.target" + "nss-lookup.target" + "nss-user-lookup.target" + "time-sync.target" + ] ++ optionals cfg.package.withCryptsetup [ + "cryptsetup.target" + "cryptsetup-pre.target" + "remote-cryptsetup.target" + ] ++ [ + "sigpwr.target" + "timers.target" + "paths.target" + "rpcbind.target" + + # Rescue mode. + "rescue.target" + "rescue.service" + + # Udev. + "systemd-tmpfiles-setup-dev-early.service" + "systemd-udevd-control.socket" + "systemd-udevd-kernel.socket" + "systemd-udevd.service" + "systemd-udev-settle.service" + ] ++ (optional (!config.boot.isContainer) "systemd-udev-trigger.service") ++ [ + # hwdb.bin is managed by NixOS + # "systemd-hwdb-update.service" + + # Consoles. + "getty.target" + "getty-pre.target" + "getty@.service" + "serial-getty@.service" + "console-getty.service" + "container-getty@.service" + "systemd-vconsole-setup.service" + + # Hardware (started by udev when a relevant device is plugged in). + "sound.target" + "bluetooth.target" + "printer.target" + "smartcard.target" + + # Kernel module loading. + "systemd-modules-load.service" + "kmod-static-nodes.service" + "modprobe@.service" + + # Filesystems. + "systemd-fsck@.service" + "systemd-fsck-root.service" + "systemd-growfs@.service" + "systemd-growfs-root.service" + "systemd-remount-fs.service" + "systemd-pstore.service" + "local-fs.target" + "local-fs-pre.target" + "remote-fs.target" + "remote-fs-pre.target" + "swap.target" + "dev-hugepages.mount" + "dev-mqueue.mount" + "sys-fs-fuse-connections.mount" + ] ++ (optional (!config.boot.isContainer) "sys-kernel-config.mount") ++ [ + "sys-kernel-debug.mount" + + # Maintaining state across reboots. + "systemd-random-seed.service" + "systemd-backlight@.service" + "systemd-rfkill.service" + "systemd-rfkill.socket" + + # Hibernate / suspend. + "hibernate.target" + "suspend.target" + "suspend-then-hibernate.target" + "sleep.target" + "hybrid-sleep.target" + "systemd-hibernate.service" + "systemd-hybrid-sleep.service" + "systemd-suspend.service" + "systemd-suspend-then-hibernate.service" + + # Reboot stuff. + "reboot.target" + "systemd-reboot.service" + "poweroff.target" + "systemd-poweroff.service" + "halt.target" + "systemd-halt.service" + "shutdown.target" + "umount.target" + "final.target" + "kexec.target" + "systemd-kexec.service" + ] ++ lib.optional cfg.package.withUtmp "systemd-update-utmp.service" ++ [ + + # Password entry. + "systemd-ask-password-console.path" + "systemd-ask-password-console.service" + "systemd-ask-password-wall.path" + "systemd-ask-password-wall.service" + + # Slices / containers. + "slices.target" + ] ++ optionals cfg.package.withImportd [ + "systemd-importd.service" + ] ++ optionals cfg.package.withMachined [ + "machine.slice" + "machines.target" + "systemd-machined.service" + ] ++ [ + "systemd-nspawn@.service" + + # Misc. + "systemd-sysctl.service" + ] ++ optionals cfg.package.withTimedated [ + "dbus-org.freedesktop.timedate1.service" + "systemd-timedated.service" + ] ++ optionals cfg.package.withLocaled [ + "dbus-org.freedesktop.locale1.service" + "systemd-localed.service" + ] ++ optionals cfg.package.withHostnamed [ + "dbus-org.freedesktop.hostname1.service" + "systemd-hostnamed.service" + ] ++ optionals cfg.package.withPortabled [ + "dbus-org.freedesktop.portable1.service" + "systemd-portabled.service" + ] ++ [ + "systemd-exit.service" + "systemd-update-done.service" + ] ++ cfg.additionalUpstreamSystemUnits; + + upstreamSystemWants = + [ "sysinit.target.wants" + "sockets.target.wants" + "local-fs.target.wants" + "multi-user.target.wants" + "timers.target.wants" + ]; + + proxy_env = config.networking.proxy.envVars; + +in + +{ + ###### interface + + options.systemd = { + + package = mkPackageOption pkgs "systemd" {}; + + units = mkOption { + description = "Definition of systemd units; see {manpage}`systemd.unit(5)`."; + default = {}; + type = systemdUtils.types.units; + }; + + packages = mkOption { + default = []; + type = types.listOf types.package; + example = literalExpression "[ pkgs.systemd-cryptsetup-generator ]"; + description = "Packages providing systemd units and hooks."; + }; + + targets = mkOption { + default = {}; + type = systemdUtils.types.targets; + description = "Definition of systemd target units; see {manpage}`systemd.target(5)`"; + }; + + services = mkOption { + default = {}; + type = systemdUtils.types.services; + description = "Definition of systemd service units; see {manpage}`systemd.service(5)`."; + }; + + sockets = mkOption { + default = {}; + type = systemdUtils.types.sockets; + description = "Definition of systemd socket units; see {manpage}`systemd.socket(5)`."; + }; + + timers = mkOption { + default = {}; + type = systemdUtils.types.timers; + description = "Definition of systemd timer units; see {manpage}`systemd.timer(5)`."; + }; + + paths = mkOption { + default = {}; + type = systemdUtils.types.paths; + description = "Definition of systemd path units; see {manpage}`systemd.path(5)`."; + }; + + mounts = mkOption { + default = []; + type = systemdUtils.types.mounts; + description = '' + Definition of systemd mount units; see {manpage}`systemd.mount(5)`. + + This is a list instead of an attrSet, because systemd mandates + the names to be derived from the `where` attribute. + ''; + }; + + automounts = mkOption { + default = []; + type = systemdUtils.types.automounts; + description = '' + Definition of systemd automount units; see {manpage}`systemd.automount(5)`. + + This is a list instead of an attrSet, because systemd mandates + the names to be derived from the `where` attribute. + ''; + }; + + slices = mkOption { + default = {}; + type = systemdUtils.types.slices; + description = "Definition of slice configurations; see {manpage}`systemd.slice(5)`."; + }; + + generators = mkOption { + type = types.attrsOf types.path; + default = {}; + example = { systemd-gpt-auto-generator = "/dev/null"; }; + description = '' + Definition of systemd generators; see {manpage}`systemd.generator(5)`. + + For each `NAME = VALUE` pair of the attrSet, a link is generated from + `/etc/systemd/system-generators/NAME` to `VALUE`. + ''; + }; + + shutdown = mkOption { + type = types.attrsOf types.path; + default = {}; + description = '' + Definition of systemd shutdown executables. + For each `NAME = VALUE` pair of the attrSet, a link is generated from + `/etc/systemd/system-shutdown/NAME` to `VALUE`. + ''; + }; + + defaultUnit = mkOption { + default = "multi-user.target"; + type = types.str; + description = '' + Default unit started when the system boots; see {manpage}`systemd.special(7)`. + ''; + }; + + ctrlAltDelUnit = mkOption { + default = "reboot.target"; + type = types.str; + example = "poweroff.target"; + description = '' + Target that should be started when Ctrl-Alt-Delete is pressed; + see {manpage}`systemd.special(7)`. + ''; + }; + + globalEnvironment = mkOption { + type = with types; attrsOf (nullOr (oneOf [ str path package ])); + default = {}; + example = { TZ = "CET"; }; + description = '' + Environment variables passed to *all* systemd units. + ''; + }; + + managerEnvironment = mkOption { + type = with types; attrsOf (nullOr (oneOf [ str path package ])); + default = {}; + example = { SYSTEMD_LOG_LEVEL = "debug"; }; + description = '' + Environment variables of PID 1. These variables are + *not* passed to started units. + ''; + }; + + enableCgroupAccounting = mkOption { + default = true; + type = types.bool; + description = '' + Whether to enable cgroup accounting; see {manpage}`cgroups(7)`. + ''; + }; + + enableUnifiedCgroupHierarchy = mkOption { + default = true; + type = types.bool; + description = '' + Whether to enable the unified cgroup hierarchy (cgroupsv2); see {manpage}`cgroups(7)`. + ''; + }; + + extraConfig = mkOption { + default = ""; + type = types.lines; + example = "DefaultLimitCORE=infinity"; + description = '' + Extra config options for systemd. See {manpage}`systemd-system.conf(5)` man page + for available options. + ''; + }; + + sleep.extraConfig = mkOption { + default = ""; + type = types.lines; + example = "HibernateDelaySec=1h"; + description = '' + Extra config options for systemd sleep state logic. + See {manpage}`sleep.conf.d(5)` man page for available options. + ''; + }; + + additionalUpstreamSystemUnits = mkOption { + default = [ ]; + type = types.listOf types.str; + example = [ "debug-shell.service" "systemd-quotacheck.service" ]; + description = '' + Additional units shipped with systemd that shall be enabled. + ''; + }; + + suppressedSystemUnits = mkOption { + default = [ ]; + type = types.listOf types.str; + example = [ "systemd-backlight@.service" ]; + description = '' + A list of units to skip when generating system systemd configuration directory. This has + priority over upstream units, {option}`systemd.units`, and + {option}`systemd.additionalUpstreamSystemUnits`. The main purpose of this is to + prevent a upstream systemd unit from being added to the initrd with any modifications made to it + by other NixOS modules. + ''; + }; + + watchdog.device = mkOption { + type = types.nullOr types.path; + default = null; + example = "/dev/watchdog"; + description = '' + The path to a hardware watchdog device which will be managed by systemd. + If not specified, systemd will default to `/dev/watchdog`. + ''; + }; + + watchdog.runtimeTime = mkOption { + type = types.nullOr types.str; + default = null; + example = "30s"; + description = '' + The amount of time which can elapse before a watchdog hardware device + will automatically reboot the system. + + Valid time units include "ms", "s", "min", "h", "d", and "w"; + see {manpage}`systemd.time(7)`. + ''; + }; + + watchdog.rebootTime = mkOption { + type = types.nullOr types.str; + default = null; + example = "10m"; + description = '' + The amount of time which can elapse after a reboot has been triggered + before a watchdog hardware device will automatically reboot the system. + If left `null`, systemd will use its default of 10 minutes; + see {manpage}`systemd-system.conf(5)`. + + Valid time units include "ms", "s", "min", "h", "d", and "w"; + see also {manpage}`systemd.time(7)`. + ''; + }; + + watchdog.kexecTime = mkOption { + type = types.nullOr types.str; + default = null; + example = "10m"; + description = '' + The amount of time which can elapse when `kexec` is being executed before + a watchdog hardware device will automatically reboot the system. This + option should only be enabled if `reloadTime` is also enabled; + see {manpage}`kexec(8)`. + + Valid time units include "ms", "s", "min", "h", "d", and "w"; + see also {manpage}`systemd.time(7)`. + ''; + }; + }; + + + ###### implementation + + config = { + + warnings = let + mkOneNetOnlineWarn = typeStr: name: def: lib.optional + (lib.elem "network-online.target" def.after && !(lib.elem "network-online.target" (def.wants ++ def.requires ++ def.bindsTo))) + "${name}.${typeStr} is ordered after 'network-online.target' but doesn't depend on it"; + mkNetOnlineWarns = typeStr: defs: lib.concatLists (lib.mapAttrsToList (mkOneNetOnlineWarn typeStr) defs); + mkMountNetOnlineWarns = typeStr: defs: lib.concatLists (map (m: mkOneNetOnlineWarn typeStr m.what m) defs); + in concatLists ( + mapAttrsToList + (name: service: + let + type = service.serviceConfig.Type or ""; + restart = service.serviceConfig.Restart or "no"; + hasDeprecated = builtins.hasAttr "StartLimitInterval" service.serviceConfig; + in + concatLists [ + (optional (type == "oneshot" && (restart == "always" || restart == "on-success")) + "Service '${name}.service' with 'Type=oneshot' cannot have 'Restart=always' or 'Restart=on-success'" + ) + (optional hasDeprecated + "Service '${name}.service' uses the attribute 'StartLimitInterval' in the Service section, which is deprecated. See https://github.com/NixOS/nixpkgs/issues/45786." + ) + (optional (service.reloadIfChanged && service.reloadTriggers != []) + "Service '${name}.service' has both 'reloadIfChanged' and 'reloadTriggers' set. This is probably not what you want, because 'reloadTriggers' behave the same whay as 'restartTriggers' if 'reloadIfChanged' is set." + ) + ] + ) + cfg.services + ) + ++ (mkNetOnlineWarns "target" cfg.targets) + ++ (mkNetOnlineWarns "service" cfg.services) + ++ (mkNetOnlineWarns "socket" cfg.sockets) + ++ (mkNetOnlineWarns "timer" cfg.timers) + ++ (mkNetOnlineWarns "path" cfg.paths) + ++ (mkMountNetOnlineWarns "mount" cfg.mounts) + ++ (mkMountNetOnlineWarns "automount" cfg.automounts) + ++ (mkNetOnlineWarns "slice" cfg.slices); + + assertions = concatLists ( + mapAttrsToList + (name: service: + map (message: { + assertion = false; + inherit message; + }) (concatLists [ + (optional ((builtins.elem "network-interfaces.target" service.after) || (builtins.elem "network-interfaces.target" service.wants)) + "Service '${name}.service' is using the deprecated target network-interfaces.target, which no longer exists. Using network.target is recommended instead." + ) + ]) + ) + cfg.services + ); + + system.build.units = cfg.units; + + system.nssModules = [ cfg.package.out ]; + system.nssDatabases = { + hosts = (mkMerge [ + (mkOrder 400 ["mymachines"]) # 400 to ensure it comes before resolve (which is mkBefore'd) + (mkOrder 999 ["myhostname"]) # after files (which is 998), but before regular nss modules + ]); + passwd = (mkMerge [ + (mkAfter [ "systemd" ]) + ]); + group = (mkMerge [ + (mkAfter [ "[success=merge] systemd" ]) # need merge so that NSS won't stop at file-based groups + ]); + }; + + environment.systemPackages = [ cfg.package ]; + + environment.etc = let + # generate contents for /etc/systemd/system-${type} from attrset of links and packages + hooks = type: links: pkgs.runCommand "system-${type}" { + preferLocalBuild = true; + packages = cfg.packages; + } '' + set -e + mkdir -p $out + for package in $packages + do + for hook in $package/lib/systemd/system-${type}/* + do + ln -s $hook $out/ + done + 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 { + type = "system"; + units = enabledUnits; + upstreamUnits = enabledUpstreamSystemUnits; + upstreamWants = upstreamSystemWants; + }; + + "systemd/system.conf".text = '' + [Manager] + ManagerEnvironment=${lib.concatStringsSep " " (lib.mapAttrsToList (n: v: "${n}=${lib.escapeShellArg v}") cfg.managerEnvironment)} + ${optionalString cfg.enableCgroupAccounting '' + DefaultCPUAccounting=yes + DefaultIOAccounting=yes + DefaultBlockIOAccounting=yes + DefaultIPAccounting=yes + ''} + DefaultLimitCORE=infinity + ${optionalString (cfg.watchdog.device != null) '' + WatchdogDevice=${cfg.watchdog.device} + ''} + ${optionalString (cfg.watchdog.runtimeTime != null) '' + RuntimeWatchdogSec=${cfg.watchdog.runtimeTime} + ''} + ${optionalString (cfg.watchdog.rebootTime != null) '' + RebootWatchdogSec=${cfg.watchdog.rebootTime} + ''} + ${optionalString (cfg.watchdog.kexecTime != null) '' + KExecWatchdogSec=${cfg.watchdog.kexecTime} + ''} + + ${cfg.extraConfig} + ''; + + "systemd/sleep.conf".text = '' + [Sleep] + ${cfg.sleep.extraConfig} + ''; + + "systemd/system-generators" = { source = hooks "generators" cfg.generators; }; + "systemd/system-shutdown" = { source = hooks "shutdown" cfg.shutdown; }; + }); + + services.dbus.enable = true; + + users.users.systemd-network = { + uid = config.ids.uids.systemd-network; + group = "systemd-network"; + }; + users.groups.systemd-network.gid = config.ids.gids.systemd-network; + users.users.systemd-resolve = { + uid = config.ids.uids.systemd-resolve; + group = "systemd-resolve"; + }; + users.groups.systemd-resolve.gid = config.ids.gids.systemd-resolve; + + # Target for ‘charon send-keys’ to hook into. + users.groups.keys.gid = config.ids.gids.keys; + + systemd.targets.keys = + { description = "Security Keys"; + unitConfig.X-StopOnReconfiguration = true; + }; + + # This target only exists so that services ordered before sysinit.target + # are restarted in the correct order, notably BEFORE the other services, + # when switching configurations. + systemd.targets.sysinit-reactivation = { + description = "Reactivate sysinit units"; + }; + + systemd.units = + mapAttrs' (n: v: nameValuePair "${n}.path" (pathToUnit n v)) cfg.paths + // mapAttrs' (n: v: nameValuePair "${n}.service" (serviceToUnit n v)) cfg.services + // mapAttrs' (n: v: nameValuePair "${n}.slice" (sliceToUnit n v)) cfg.slices + // mapAttrs' (n: v: nameValuePair "${n}.socket" (socketToUnit n v)) cfg.sockets + // mapAttrs' (n: v: nameValuePair "${n}.target" (targetToUnit n v)) cfg.targets + // mapAttrs' (n: v: nameValuePair "${n}.timer" (timerToUnit n v)) cfg.timers + // listToAttrs (map + (v: let n = escapeSystemdPath v.where; + in nameValuePair "${n}.mount" (mountToUnit n v)) cfg.mounts) + // listToAttrs (map + (v: let n = escapeSystemdPath v.where; + in nameValuePair "${n}.automount" (automountToUnit n v)) cfg.automounts); + + # Environment of PID 1 + systemd.managerEnvironment = { + # Doesn't contain systemd itself - everything works so it seems to use the compiled-in value for its tools + # util-linux is needed for the main fsck utility wrapping the fs-specific ones + PATH = lib.makeBinPath (config.system.fsPackages ++ [cfg.package.util-linux]); + LOCALE_ARCHIVE = "/run/current-system/sw/lib/locale/locale-archive"; + TZDIR = "/etc/zoneinfo"; + # If SYSTEMD_UNIT_PATH ends with an empty component (":"), the usual unit load path will be appended to the contents of the variable + SYSTEMD_UNIT_PATH = lib.mkIf (config.boot.extraSystemdUnitPaths != []) "${builtins.concatStringsSep ":" config.boot.extraSystemdUnitPaths}:"; + }; + + + system.requiredKernelConfig = map config.lib.kernelConfig.isEnabled + [ "DEVTMPFS" "CGROUPS" "INOTIFY_USER" "SIGNALFD" "TIMERFD" "EPOLL" "NET" + "SYSFS" "PROC_FS" "FHANDLE" "CRYPTO_USER_API_HASH" "CRYPTO_HMAC" + "CRYPTO_SHA256" "DMIID" "AUTOFS_FS" "TMPFS_POSIX_ACL" + "TMPFS_XATTR" "SECCOMP" + ]; + + # Generate timer units for all services that have a ‘startAt’ value. + systemd.timers = + mapAttrs (name: service: + { wantedBy = [ "timers.target" ]; + timerConfig.OnCalendar = service.startAt; + }) + (filterAttrs (name: service: service.enable && service.startAt != []) cfg.services); + + # Some overrides to upstream units. + systemd.services."systemd-backlight@".restartIfChanged = false; + systemd.services."systemd-fsck@".restartIfChanged = false; + systemd.services."systemd-fsck@".path = [ pkgs.util-linux ] ++ config.system.fsPackages; + systemd.services."systemd-makefs@" = { + restartIfChanged = false; + path = [ pkgs.util-linux ] ++ config.system.fsPackages; + # Since there is no /etc/systemd/system/systemd-makefs@.service + # file, the units generated in /run/systemd/generator would + # override anything we put here. But by forcing the use of a + # drop-in in /etc, it does apply. + overrideStrategy = "asDropin"; + }; + systemd.services."systemd-mkswap@" = { + restartIfChanged = false; + path = [ pkgs.util-linux ]; + overrideStrategy = "asDropin"; + }; + systemd.services.systemd-random-seed.restartIfChanged = false; + systemd.services.systemd-remount-fs.restartIfChanged = false; + systemd.services.systemd-update-utmp.restartIfChanged = false; + systemd.services.systemd-udev-settle.restartIfChanged = false; # Causes long delays in nixos-rebuild + systemd.targets.local-fs.unitConfig.X-StopOnReconfiguration = true; + systemd.targets.remote-fs.unitConfig.X-StopOnReconfiguration = true; + systemd.targets.network-online.wantedBy = [ "multi-user.target" ]; + systemd.services.systemd-importd.environment = proxy_env; + systemd.services.systemd-pstore.wantedBy = [ "sysinit.target" ]; # see #81138 + + # NixOS has kernel modules in a different location, so override that here. + systemd.services.kmod-static-nodes.unitConfig.ConditionFileNotEmpty = [ + "" # required to unset the previous value! + "/run/booted-system/kernel-modules/lib/modules/%v/modules.devname" + ]; + + # Don't bother with certain units in containers. + systemd.services.systemd-remount-fs.unitConfig.ConditionVirtualization = "!container"; + systemd.services.systemd-random-seed.unitConfig.ConditionVirtualization = "!container"; + + # Increase numeric PID range (set directly instead of copying a one-line file from systemd) + # https://github.com/systemd/systemd/pull/12226 + boot.kernel.sysctl."kernel.pid_max" = mkIf pkgs.stdenv.is64bit (lib.mkDefault 4194304); + + boot.kernelParams = optional (!cfg.enableUnifiedCgroupHierarchy) "systemd.unified_cgroup_hierarchy=0"; + + # Avoid potentially degraded system state due to + # "Userspace Out-Of-Memory (OOM) Killer was skipped because of a failed condition check (ConditionControlGroupController=v2)." + systemd.oomd.enable = mkIf (!cfg.enableUnifiedCgroupHierarchy) false; + + services.logrotate.settings = { + "/var/log/btmp" = mapAttrs (_: mkDefault) { + frequency = "monthly"; + rotate = 1; + create = "0660 root ${config.users.groups.utmp.name}"; + minsize = "1M"; + }; + "/var/log/wtmp" = mapAttrs (_: mkDefault) { + frequency = "monthly"; + rotate = 1; + create = "0664 root ${config.users.groups.utmp.name}"; + minsize = "1M"; + }; + }; + }; + + # FIXME: Remove these eventually. + imports = + [ (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.") + ]; +} diff --git a/nixpkgs/nixos/modules/system/boot/systemd/coredump.nix b/nixpkgs/nixos/modules/system/boot/systemd/coredump.nix new file mode 100644 index 000000000000..03ef00e5683c --- /dev/null +++ b/nixpkgs/nixos/modules/system/boot/systemd/coredump.nix @@ -0,0 +1,78 @@ +{ config, lib, pkgs, utils, ... }: + +with lib; + +let + cfg = config.systemd.coredump; + systemd = config.systemd.package; +in { + options = { + systemd.coredump.enable = mkOption { + default = true; + type = types.bool; + description = lib.mdDoc '' + Whether core dumps should be processed by + {command}`systemd-coredump`. If disabled, core dumps + appear in the current directory of the crashing process. + ''; + }; + + systemd.coredump.extraConfig = mkOption { + default = ""; + type = types.lines; + example = "Storage=journal"; + description = lib.mdDoc '' + Extra config options for systemd-coredump. See coredump.conf(5) man page + for available options. + ''; + }; + }; + + config = mkMerge [ + + (mkIf cfg.enable { + systemd.additionalUpstreamSystemUnits = [ + "systemd-coredump.socket" + "systemd-coredump@.service" + ]; + + environment.etc = { + "systemd/coredump.conf".text = + '' + [Coredump] + ${cfg.extraConfig} + ''; + + # install provided sysctl snippets + "sysctl.d/50-coredump.conf".source = + # Fix systemd-coredump error caused by truncation of `kernel.core_pattern` + # when the `systemd` derivation name is too long. This works by substituting + # the path to `systemd` with a symlink that has a constant-length path. + # + # See: https://github.com/NixOS/nixpkgs/issues/213408 + pkgs.substitute { + src = "${systemd}/example/sysctl.d/50-coredump.conf"; + replacements = [ + "--replace" + "${systemd}" + "${pkgs.symlinkJoin { name = "systemd"; paths = [ systemd ]; }}" + ]; + }; + + "sysctl.d/50-default.conf".source = "${systemd}/example/sysctl.d/50-default.conf"; + }; + + users.users.systemd-coredump = { + uid = config.ids.uids.systemd-coredump; + group = "systemd-coredump"; + }; + users.groups.systemd-coredump = {}; + }) + + (mkIf (!cfg.enable) { + boot.kernel.sysctl."kernel.core_pattern" = mkDefault "core"; + }) + + ]; + +} diff --git a/nixpkgs/nixos/modules/system/boot/systemd/homed.nix b/nixpkgs/nixos/modules/system/boot/systemd/homed.nix new file mode 100644 index 000000000000..b216820c0c0c --- /dev/null +++ b/nixpkgs/nixos/modules/system/boot/systemd/homed.nix @@ -0,0 +1,43 @@ +{ config, lib, pkgs, ... }: + +let + cfg = config.services.homed; +in +{ + options.services.homed.enable = lib.mkEnableOption (lib.mdDoc '' + systemd home area/user account manager + ''); + + config = lib.mkIf cfg.enable { + assertions = [ + { + assertion = config.services.nscd.enable; + message = "systemd-homed requires the use of systemd nss module. services.nscd.enable must be set to true,"; + } + ]; + + systemd.additionalUpstreamSystemUnits = [ + "systemd-homed.service" + "systemd-homed-activate.service" + ]; + + # This is mentioned in homed's [Install] section. + # + # While homed appears to work without it, it's probably better + # to follow upstream recommendations. + services.userdbd.enable = lib.mkDefault true; + + systemd.services = { + systemd-homed = { + # These packages are required to manage encrypted volumes + path = config.system.fsPackages; + aliases = [ "dbus-org.freedesktop.home1.service" ]; + wantedBy = [ "multi-user.target" ]; + }; + + systemd-homed-activate = { + wantedBy = [ "systemd-homed.service" ]; + }; + }; + }; +} diff --git a/nixpkgs/nixos/modules/system/boot/systemd/initrd-secrets.nix b/nixpkgs/nixos/modules/system/boot/systemd/initrd-secrets.nix new file mode 100644 index 000000000000..d375238aa146 --- /dev/null +++ b/nixpkgs/nixos/modules/system/boot/systemd/initrd-secrets.nix @@ -0,0 +1,37 @@ +{ config, pkgs, lib, ... }: + +{ + config = lib.mkIf (config.boot.initrd.enable && config.boot.initrd.systemd.enable) { + # Copy secrets into the initrd if they cannot be appended + boot.initrd.systemd.contents = lib.mkIf (!config.boot.loader.supportsInitrdSecrets) + (lib.mapAttrs' (dest: source: lib.nameValuePair "/.initrd-secrets/${dest}" { source = if source == null then dest else source; }) config.boot.initrd.secrets); + + # Copy secrets to their respective locations + boot.initrd.systemd.services.initrd-nixos-copy-secrets = lib.mkIf (config.boot.initrd.secrets != {}) { + description = "Copy secrets into place"; + # Run as early as possible + wantedBy = [ "sysinit.target" ]; + before = [ "cryptsetup-pre.target" "shutdown.target" ]; + conflicts = [ "shutdown.target" ]; + unitConfig.DefaultDependencies = false; + + # We write the secrets to /.initrd-secrets and move them because this allows + # secrets to be written to /run. If we put the secret directly to /run and + # drop this service, we'd mount the /run tmpfs over the secret, making it + # invisible in stage 2. + script = '' + for secret in $(cd /.initrd-secrets; find . -type f -o -type l); do + mkdir -p "$(dirname "/$secret")" + cp "/.initrd-secrets/$secret" "/$secret" + done + ''; + + serviceConfig = { + Type = "oneshot"; + RemainAfterExit = true; + }; + }; + # The script needs this + boot.initrd.systemd.extraBin.find = "${pkgs.findutils}/bin/find"; + }; +} diff --git a/nixpkgs/nixos/modules/system/boot/systemd/initrd.nix b/nixpkgs/nixos/modules/system/boot/systemd/initrd.nix new file mode 100644 index 000000000000..9641921fc795 --- /dev/null +++ b/nixpkgs/nixos/modules/system/boot/systemd/initrd.nix @@ -0,0 +1,562 @@ +{ lib, options, config, utils, pkgs, ... }: + +with lib; + +let + inherit (utils) systemdUtils escapeSystemdPath; + inherit (systemdUtils.lib) + generateUnits + pathToUnit + serviceToUnit + sliceToUnit + socketToUnit + targetToUnit + timerToUnit + mountToUnit + automountToUnit; + + + cfg = config.boot.initrd.systemd; + + # Copied from fedora + upstreamUnits = [ + "basic.target" + "ctrl-alt-del.target" + "emergency.service" + "emergency.target" + "final.target" + "halt.target" + "initrd-cleanup.service" + "initrd-fs.target" + "initrd-parse-etc.service" + "initrd-root-device.target" + "initrd-root-fs.target" + "initrd-switch-root.service" + "initrd-switch-root.target" + "initrd.target" + "kexec.target" + "kmod-static-nodes.service" + "local-fs-pre.target" + "local-fs.target" + "multi-user.target" + "paths.target" + "poweroff.target" + "reboot.target" + "rescue.service" + "rescue.target" + "rpcbind.target" + "shutdown.target" + "sigpwr.target" + "slices.target" + "sockets.target" + "swap.target" + "sysinit.target" + "sys-kernel-config.mount" + "syslog.socket" + "systemd-ask-password-console.path" + "systemd-ask-password-console.service" + "systemd-fsck@.service" + "systemd-halt.service" + "systemd-hibernate-resume.service" + "systemd-journald-audit.socket" + "systemd-journald-dev-log.socket" + "systemd-journald.service" + "systemd-journald.socket" + "systemd-kexec.service" + "systemd-modules-load.service" + "systemd-poweroff.service" + "systemd-reboot.service" + "systemd-sysctl.service" + "systemd-tmpfiles-setup-dev.service" + "systemd-tmpfiles-setup.service" + "timers.target" + "umount.target" + "systemd-bsod.service" + ] ++ cfg.additionalUpstreamUnits; + + upstreamWants = [ + "sysinit.target.wants" + ]; + + enabledUpstreamUnits = filter (n: ! elem n cfg.suppressedUnits) upstreamUnits; + enabledUnits = filterAttrs (n: v: ! elem n cfg.suppressedUnits) cfg.units; + jobScripts = concatLists (mapAttrsToList (_: unit: unit.jobScripts or []) (filterAttrs (_: v: v.enable) cfg.services)); + + stage1Units = generateUnits { + type = "initrd"; + units = enabledUnits; + upstreamUnits = enabledUpstreamUnits; + inherit upstreamWants; + inherit (cfg) packages package; + }; + + fileSystems = filter utils.fsNeededForBoot config.system.build.fileSystems; + + kernel-name = config.boot.kernelPackages.kernel.name or "kernel"; + modulesTree = config.system.modulesTree.override { name = kernel-name + "-modules"; }; + firmware = config.hardware.firmware; + # Determine the set of modules that we need to mount the root FS. + modulesClosure = pkgs.makeModulesClosure { + rootModules = config.boot.initrd.availableKernelModules ++ config.boot.initrd.kernelModules; + kernel = modulesTree; + firmware = firmware; + allowMissing = false; + }; + + initrdBinEnv = pkgs.buildEnv { + name = "initrd-bin-env"; + paths = map getBin cfg.initrdBin; + pathsToLink = ["/bin" "/sbin"]; + postBuild = concatStringsSep "\n" (mapAttrsToList (n: v: "ln -sf '${v}' $out/bin/'${n}'") cfg.extraBin); + }; + + initialRamdisk = pkgs.makeInitrdNG { + name = "initrd-${kernel-name}"; + inherit (config.boot.initrd) compressor compressorArgs prepend; + inherit (cfg) strip; + + contents = map (path: { object = path; symlink = ""; }) (subtractLists cfg.suppressedStorePaths cfg.storePaths) + ++ mapAttrsToList (_: v: { object = v.source; symlink = v.target; }) (filterAttrs (_: v: v.enable) cfg.contents); + }; + +in { + options.boot.initrd.systemd = { + enable = mkEnableOption (lib.mdDoc "systemd in initrd") // { + description = lib.mdDoc '' + Whether to enable systemd in initrd. The unit options such as + {option}`boot.initrd.systemd.services` are the same as their + stage 2 counterparts such as {option}`systemd.services`, + except that `restartTriggers` and `reloadTriggers` are not + supported. + ''; + }; + + package = lib.mkOption { + type = lib.types.package; + default = config.systemd.package; + defaultText = lib.literalExpression "config.systemd.package"; + description = '' + The systemd package to use. + ''; + }; + + extraConfig = mkOption { + default = ""; + type = types.lines; + example = "DefaultLimitCORE=infinity"; + description = lib.mdDoc '' + Extra config options for systemd. See systemd-system.conf(5) man page + for available options. + ''; + }; + + managerEnvironment = mkOption { + type = with types; attrsOf (nullOr (oneOf [ str path package ])); + default = {}; + example = { SYSTEMD_LOG_LEVEL = "debug"; }; + description = lib.mdDoc '' + Environment variables of PID 1. These variables are + *not* passed to started units. + ''; + }; + + contents = mkOption { + description = lib.mdDoc "Set of files that have to be linked into the initrd"; + example = literalExpression '' + { + "/etc/hostname".text = "mymachine"; + } + ''; + default = {}; + type = utils.systemdUtils.types.initrdContents; + }; + + storePaths = mkOption { + description = lib.mdDoc '' + Store paths to copy into the initrd as well. + ''; + type = with types; listOf (oneOf [ singleLineStr package ]); + default = []; + }; + + strip = mkOption { + description = lib.mdDoc '' + Whether to completely strip executables and libraries copied to the initramfs. + + Setting this to false may save on the order of 30MiB on the + machine building the system (by avoiding a binutils + reference), at the cost of ~1MiB of initramfs size. This puts + this option firmly in the territory of micro-optimisation. + ''; + type = types.bool; + default = true; + }; + + extraBin = mkOption { + description = lib.mdDoc '' + Tools to add to /bin + ''; + example = literalExpression '' + { + umount = ''${pkgs.util-linux}/bin/umount; + } + ''; + type = types.attrsOf types.path; + default = {}; + }; + + suppressedStorePaths = mkOption { + description = lib.mdDoc '' + Store paths specified in the storePaths option that + should not be copied. + ''; + type = types.listOf types.singleLineStr; + default = []; + }; + + emergencyAccess = mkOption { + type = with types; oneOf [ bool (nullOr (passwdEntry str)) ]; + description = lib.mdDoc '' + Set to true for unauthenticated emergency access, and false for + no emergency access. + + Can also be set to a hashed super user password to allow + authenticated access to the emergency mode. + ''; + default = false; + }; + + initrdBin = mkOption { + type = types.listOf types.package; + default = []; + description = lib.mdDoc '' + Packages to include in /bin for the stage 1 emergency shell. + ''; + }; + + additionalUpstreamUnits = mkOption { + default = [ ]; + type = types.listOf types.str; + example = [ "debug-shell.service" "systemd-quotacheck.service" ]; + description = lib.mdDoc '' + Additional units shipped with systemd that shall be enabled. + ''; + }; + + suppressedUnits = mkOption { + default = [ ]; + type = types.listOf types.str; + example = [ "systemd-backlight@.service" ]; + description = lib.mdDoc '' + A list of units to skip when generating system systemd configuration directory. This has + priority over upstream units, {option}`boot.initrd.systemd.units`, and + {option}`boot.initrd.systemd.additionalUpstreamUnits`. The main purpose of this is to + prevent a upstream systemd unit from being added to the initrd with any modifications made to it + by other NixOS modules. + ''; + }; + + units = mkOption { + description = lib.mdDoc "Definition of systemd units."; + default = {}; + visible = "shallow"; + type = systemdUtils.types.units; + }; + + packages = mkOption { + default = []; + type = types.listOf types.package; + example = literalExpression "[ pkgs.systemd-cryptsetup-generator ]"; + description = lib.mdDoc "Packages providing systemd units and hooks."; + }; + + targets = mkOption { + default = {}; + visible = "shallow"; + type = systemdUtils.types.initrdTargets; + description = lib.mdDoc "Definition of systemd target units."; + }; + + services = mkOption { + default = {}; + type = systemdUtils.types.initrdServices; + visible = "shallow"; + description = lib.mdDoc "Definition of systemd service units."; + }; + + sockets = mkOption { + default = {}; + type = systemdUtils.types.initrdSockets; + visible = "shallow"; + description = lib.mdDoc "Definition of systemd socket units."; + }; + + timers = mkOption { + default = {}; + type = systemdUtils.types.initrdTimers; + visible = "shallow"; + description = lib.mdDoc "Definition of systemd timer units."; + }; + + paths = mkOption { + default = {}; + type = systemdUtils.types.initrdPaths; + visible = "shallow"; + description = lib.mdDoc "Definition of systemd path units."; + }; + + mounts = mkOption { + default = []; + type = systemdUtils.types.initrdMounts; + visible = "shallow"; + description = lib.mdDoc '' + Definition of systemd mount units. + This is a list instead of an attrSet, because systemd mandates the names to be derived from + the 'where' attribute. + ''; + }; + + automounts = mkOption { + default = []; + type = systemdUtils.types.automounts; + visible = "shallow"; + description = lib.mdDoc '' + Definition of systemd automount units. + This is a list instead of an attrSet, because systemd mandates the names to be derived from + the 'where' attribute. + ''; + }; + + slices = mkOption { + default = {}; + type = systemdUtils.types.slices; + visible = "shallow"; + description = lib.mdDoc "Definition of slice configurations."; + }; + + enableTpm2 = mkOption { + default = true; + type = types.bool; + description = lib.mdDoc '' + Whether to enable TPM2 support in the initrd. + ''; + }; + }; + + config = mkIf (config.boot.initrd.enable && cfg.enable) { + assertions = map (name: { + assertion = lib.attrByPath name (throw "impossible") config.boot.initrd == ""; + message = '' + systemd stage 1 does not support 'boot.initrd.${lib.concatStringsSep "." name}'. Please + convert it to analogous systemd units in 'boot.initrd.systemd'. + + Definitions: + ${lib.concatMapStringsSep "\n" ({ file, ... }: " - ${file}") (lib.attrByPath name (throw "impossible") options.boot.initrd).definitionsWithLocations} + ''; + }) [ + [ "preFailCommands" ] + [ "preDeviceCommands" ] + [ "preLVMCommands" ] + [ "postDeviceCommands" ] + [ "postResumeCommands" ] + [ "postMountCommands" ] + [ "extraUdevRulesCommands" ] + [ "extraUtilsCommands" ] + [ "extraUtilsCommandsTest" ] + [ "network" "postCommands" ] + ]; + + system.build = { inherit initialRamdisk; }; + + boot.initrd.availableKernelModules = [ + # systemd needs this for some features + "autofs" + # systemd-cryptenroll + ] ++ lib.optional cfg.enableTpm2 "tpm-tis" + ++ lib.optional (cfg.enableTpm2 && !(pkgs.stdenv.hostPlatform.isRiscV64 || pkgs.stdenv.hostPlatform.isArmv7)) "tpm-crb"; + + boot.initrd.systemd = { + initrdBin = [pkgs.bash pkgs.coreutils cfg.package.kmod cfg.package]; + extraBin = { + less = "${pkgs.less}/bin/less"; + mount = "${cfg.package.util-linux}/bin/mount"; + umount = "${cfg.package.util-linux}/bin/umount"; + fsck = "${cfg.package.util-linux}/bin/fsck"; + }; + + managerEnvironment.PATH = "/bin:/sbin"; + + contents = { + "/tmp/.keep".text = "systemd requires the /tmp mount point in the initrd cpio archive"; + "/init".source = "${cfg.package}/lib/systemd/systemd"; + "/etc/systemd/system".source = stage1Units; + + "/etc/systemd/system.conf".text = '' + [Manager] + DefaultEnvironment=PATH=/bin:/sbin + ${cfg.extraConfig} + ManagerEnvironment=${lib.concatStringsSep " " (lib.mapAttrsToList (n: v: "${n}=${lib.escapeShellArg v}") cfg.managerEnvironment)} + ''; + + "/lib".source = "${modulesClosure}/lib"; + + "/etc/modules-load.d/nixos.conf".text = concatStringsSep "\n" config.boot.initrd.kernelModules; + + # We can use either ! or * to lock the root account in the + # console, but some software like OpenSSH won't even allow you + # to log in with an SSH key if you use ! so we use * instead + "/etc/shadow".text = "root:${if isBool cfg.emergencyAccess then optionalString (!cfg.emergencyAccess) "*" else cfg.emergencyAccess}:::::::"; + + "/bin".source = "${initrdBinEnv}/bin"; + "/sbin".source = "${initrdBinEnv}/sbin"; + + "/etc/sysctl.d/nixos.conf".text = "kernel.modprobe = /sbin/modprobe"; + "/etc/modprobe.d/systemd.conf".source = "${cfg.package}/lib/modprobe.d/systemd.conf"; + "/etc/modprobe.d/ubuntu.conf".source = pkgs.runCommand "initrd-kmod-blacklist-ubuntu" { } '' + ${pkgs.buildPackages.perl}/bin/perl -0pe 's/## file: iwlwifi.conf(.+?)##/##/s;' $src > $out + ''; + "/etc/modprobe.d/debian.conf".source = pkgs.kmod-debian-aliases; + + "/etc/os-release".source = config.boot.initrd.osRelease; + "/etc/initrd-release".source = config.boot.initrd.osRelease; + + } // optionalAttrs (config.environment.etc ? "modprobe.d/nixos.conf") { + "/etc/modprobe.d/nixos.conf".source = config.environment.etc."modprobe.d/nixos.conf".source; + }; + + storePaths = [ + # systemd tooling + "${cfg.package}/lib/systemd/systemd-executor" + "${cfg.package}/lib/systemd/systemd-fsck" + "${cfg.package}/lib/systemd/systemd-hibernate-resume" + "${cfg.package}/lib/systemd/systemd-journald" + "${cfg.package}/lib/systemd/systemd-makefs" + "${cfg.package}/lib/systemd/systemd-modules-load" + "${cfg.package}/lib/systemd/systemd-remount-fs" + "${cfg.package}/lib/systemd/systemd-shutdown" + "${cfg.package}/lib/systemd/systemd-sulogin-shell" + "${cfg.package}/lib/systemd/systemd-sysctl" + "${cfg.package}/lib/systemd/systemd-bsod" + + # generators + "${cfg.package}/lib/systemd/system-generators/systemd-debug-generator" + "${cfg.package}/lib/systemd/system-generators/systemd-fstab-generator" + "${cfg.package}/lib/systemd/system-generators/systemd-gpt-auto-generator" + "${cfg.package}/lib/systemd/system-generators/systemd-hibernate-resume-generator" + "${cfg.package}/lib/systemd/system-generators/systemd-run-generator" + + # utilities needed by systemd + "${cfg.package.util-linux}/bin/mount" + "${cfg.package.util-linux}/bin/umount" + "${cfg.package.util-linux}/bin/sulogin" + + # so NSS can look up usernames + "${pkgs.glibc}/lib/libnss_files.so.2" + ] ++ optionals (cfg.package.withCryptsetup && cfg.enableTpm2) [ + # tpm2 support + "${cfg.package}/lib/cryptsetup/libcryptsetup-token-systemd-tpm2.so" + pkgs.tpm2-tss + ] ++ optionals cfg.package.withCryptsetup [ + # fido2 support + "${cfg.package}/lib/cryptsetup/libcryptsetup-token-systemd-fido2.so" + "${pkgs.libfido2}/lib/libfido2.so.1" + ] ++ jobScripts; + + targets.initrd.aliases = ["default.target"]; + units = + mapAttrs' (n: v: nameValuePair "${n}.path" (pathToUnit n v)) cfg.paths + // mapAttrs' (n: v: nameValuePair "${n}.service" (serviceToUnit n v)) cfg.services + // mapAttrs' (n: v: nameValuePair "${n}.slice" (sliceToUnit n v)) cfg.slices + // mapAttrs' (n: v: nameValuePair "${n}.socket" (socketToUnit n v)) cfg.sockets + // mapAttrs' (n: v: nameValuePair "${n}.target" (targetToUnit n v)) cfg.targets + // mapAttrs' (n: v: nameValuePair "${n}.timer" (timerToUnit n v)) cfg.timers + // listToAttrs (map + (v: let n = escapeSystemdPath v.where; + in nameValuePair "${n}.mount" (mountToUnit n v)) cfg.mounts) + // listToAttrs (map + (v: let n = escapeSystemdPath v.where; + in nameValuePair "${n}.automount" (automountToUnit n v)) cfg.automounts); + + # make sure all the /dev nodes are set up + services.systemd-tmpfiles-setup-dev.wantedBy = ["sysinit.target"]; + + services.initrd-nixos-activation = { + after = [ "initrd-fs.target" ]; + requiredBy = [ "initrd.target" ]; + unitConfig.AssertPathExists = "/etc/initrd-release"; + serviceConfig.Type = "oneshot"; + description = "NixOS Activation"; + + script = /* bash */ '' + set -uo pipefail + export PATH="/bin:${cfg.package.util-linux}/bin" + + # Figure out what closure to boot + closure= + for o in $(< /proc/cmdline); do + case $o in + init=*) + IFS== read -r -a initParam <<< "$o" + closure="$(dirname "''${initParam[1]}")" + ;; + esac + done + + # Sanity check + if [ -z "''${closure:-}" ]; then + echo 'No init= parameter on the kernel command line' >&2 + exit 1 + fi + + # If we are not booting a NixOS closure (e.g. init=/bin/sh), + # we don't know what root to prepare so we don't do anything + if ! [ -x "/sysroot$(readlink "/sysroot$closure/prepare-root" || echo "$closure/prepare-root")" ]; then + echo "NEW_INIT=''${initParam[1]}" > /etc/switch-root.conf + echo "$closure does not look like a NixOS installation - not activating" + exit 0 + fi + echo 'NEW_INIT=' > /etc/switch-root.conf + + + # We need to propagate /run for things like /run/booted-system + # and /run/current-system. + mkdir -p /sysroot/run + mount --bind /run /sysroot/run + + # Initialize the system + export IN_NIXOS_SYSTEMD_STAGE1=true + exec chroot /sysroot $closure/prepare-root + ''; + }; + + # This will either call systemctl with the new init as the last parameter (which + # is the case when not booting a NixOS system) or with an empty string, causing + # systemd to bypass its verification code that checks whether the next file is a systemd + # and using its compiled-in value + services.initrd-switch-root.serviceConfig = { + EnvironmentFile = "-/etc/switch-root.conf"; + ExecStart = [ + "" + ''systemctl --no-block switch-root /sysroot "''${NEW_INIT}"'' + ]; + }; + + services.panic-on-fail = { + wantedBy = ["emergency.target"]; + unitConfig = { + DefaultDependencies = false; + ConditionKernelCommandLine = [ + "|boot.panic_on_fail" + "|stage1panic" + ]; + }; + script = '' + echo c > /proc/sysrq-trigger + ''; + serviceConfig.Type = "oneshot"; + }; + }; + + boot.kernelParams = lib.mkIf (config.boot.resumeDevice != "") [ "resume=${config.boot.resumeDevice}" ]; + }; +} diff --git a/nixpkgs/nixos/modules/system/boot/systemd/journald-gateway.nix b/nixpkgs/nixos/modules/system/boot/systemd/journald-gateway.nix new file mode 100644 index 000000000000..854965282344 --- /dev/null +++ b/nixpkgs/nixos/modules/system/boot/systemd/journald-gateway.nix @@ -0,0 +1,135 @@ +{ config, lib, pkgs, ... }: + +let + cfg = config.services.journald.gateway; + + cliArgs = lib.cli.toGNUCommandLineShell { } { + # If either of these are null / false, they are not passed in the command-line + inherit (cfg) cert key trust system user merge; + }; +in +{ + meta.maintainers = [ lib.maintainers.raitobezarius ]; + options.services.journald.gateway = { + enable = lib.mkEnableOption "the HTTP gateway to the journal"; + + port = lib.mkOption { + default = 19531; + type = lib.types.port; + description = '' + The port to listen to. + ''; + }; + + cert = lib.mkOption { + default = null; + type = with lib.types; nullOr str; + description = lib.mdDoc '' + The path to a file or `AF_UNIX` stream socket to read the server + certificate from. + + The certificate must be in PEM format. This option switches + `systemd-journal-gatewayd` into HTTPS mode and must be used together + with {option}`services.journald.gateway.key`. + ''; + }; + + key = lib.mkOption { + default = null; + type = with lib.types; nullOr str; + description = lib.mdDoc '' + Specify the path to a file or `AF_UNIX` stream socket to read the + secret server key corresponding to the certificate specified with + {option}`services.journald.gateway.cert` from. + + The key must be in PEM format. + + This key should not be world-readable, and must be readably by the + `systemd-journal-gateway` user. + ''; + }; + + trust = lib.mkOption { + default = null; + type = with lib.types; nullOr str; + description = lib.mdDoc '' + Specify the path to a file or `AF_UNIX` stream socket to read a CA + certificate from. + + The certificate must be in PEM format. + + Setting this option enforces client certificate checking. + ''; + }; + + system = lib.mkOption { + default = true; + type = lib.types.bool; + description = lib.mdDoc '' + Serve entries from system services and the kernel. + + This has the same meaning as `--system` for {manpage}`journalctl(1)`. + ''; + }; + + user = lib.mkOption { + default = true; + type = lib.types.bool; + description = lib.mdDoc '' + Serve entries from services for the current user. + + This has the same meaning as `--user` for {manpage}`journalctl(1)`. + ''; + }; + + merge = lib.mkOption { + default = false; + type = lib.types.bool; + description = lib.mdDoc '' + Serve entries interleaved from all available journals, including other + machines. + + This has the same meaning as `--merge` option for + {manpage}`journalctl(1)`. + ''; + }; + }; + + config = lib.mkIf cfg.enable { + assertions = [ + { + # This prevents the weird case were disabling "system" and "user" + # actually enables both because the cli flags are not present. + assertion = cfg.system || cfg.user; + message = '' + systemd-journal-gatewayd cannot serve neither "system" nor "user" + journals. + ''; + } + ]; + + systemd.additionalUpstreamSystemUnits = [ + "systemd-journal-gatewayd.socket" + "systemd-journal-gatewayd.service" + ]; + + users.users.systemd-journal-gateway.uid = config.ids.uids.systemd-journal-gateway; + users.users.systemd-journal-gateway.group = "systemd-journal-gateway"; + users.groups.systemd-journal-gateway.gid = config.ids.gids.systemd-journal-gateway; + + systemd.services.systemd-journal-gatewayd.serviceConfig.ExecStart = [ + # Clear the default command line + "" + "${pkgs.systemd}/lib/systemd/systemd-journal-gatewayd ${cliArgs}" + ]; + + systemd.sockets.systemd-journal-gatewayd = { + wantedBy = [ "sockets.target" ]; + listenStreams = [ + # Clear the default port + "" + (toString cfg.port) + ]; + }; + }; +} diff --git a/nixpkgs/nixos/modules/system/boot/systemd/journald-remote.nix b/nixpkgs/nixos/modules/system/boot/systemd/journald-remote.nix new file mode 100644 index 000000000000..57a0a133e1c6 --- /dev/null +++ b/nixpkgs/nixos/modules/system/boot/systemd/journald-remote.nix @@ -0,0 +1,163 @@ +{ config, lib, pkgs, ... }: + +let + cfg = config.services.journald.remote; + format = pkgs.formats.systemd; + + cliArgs = lib.cli.toGNUCommandLineShell { } { + inherit (cfg) output; + # "-3" specifies the file descriptor from the .socket unit. + "listen-${cfg.listen}" = "-3"; + }; +in +{ + meta.maintainers = [ lib.maintainers.raitobezarius ]; + options.services.journald.remote = { + enable = lib.mkEnableOption "receiving systemd journals from the network"; + + listen = lib.mkOption { + default = "https"; + type = lib.types.enum [ "https" "http" ]; + description = lib.mdDoc '' + Which protocol to listen to. + ''; + }; + + output = lib.mkOption { + default = "/var/log/journal/remote/"; + type = lib.types.str; + description = lib.mdDoc '' + The location of the output journal. + + In case the output file is not specified, journal files will be created + underneath the selected directory. Files will be called + {file}`remote-hostname.journal`, where the `hostname` part is the + escaped hostname of the source endpoint of the connection, or the + numerical address if the hostname cannot be determined. + ''; + }; + + port = lib.mkOption { + default = 19532; + type = lib.types.port; + description = '' + The port to listen to. + + Note that this option is used only if + {option}`services.journald.upload.listen` is configured to be either + "https" or "http". + ''; + }; + + settings = lib.mkOption { + default = { }; + + description = lib.mdDoc '' + Configuration in the journal-remote configuration file. See + {manpage}`journal-remote.conf(5)` for available options. + ''; + + type = lib.types.submodule { + freeformType = format.type; + + options.Remote = { + Seal = lib.mkOption { + default = false; + example = true; + type = lib.types.bool; + description = '' + Periodically sign the data in the journal using Forward Secure + Sealing. + ''; + }; + + SplitMode = lib.mkOption { + default = "host"; + example = "none"; + type = lib.types.enum [ "host" "none" ]; + description = lib.mdDoc '' + With "host", a separate output file is used, based on the + hostname of the other endpoint of a connection. With "none", only + one output journal file is used. + ''; + }; + + ServerKeyFile = lib.mkOption { + default = "/etc/ssl/private/journal-remote.pem"; + type = lib.types.str; + description = lib.mdDoc '' + A path to a SSL secret key file in PEM format. + + Note that due to security reasons, `systemd-journal-remote` will + refuse files from the world-readable `/nix/store`. This file + should be readable by the "" user. + + This option can be used with `listen = "https"`. If the path + refers to an `AF_UNIX` stream socket in the file system a + connection is made to it and the key read from it. + ''; + }; + + ServerCertificateFile = lib.mkOption { + default = "/etc/ssl/certs/journal-remote.pem"; + type = lib.types.str; + description = lib.mdDoc '' + A path to a SSL certificate file in PEM format. + + This option can be used with `listen = "https"`. If the path + refers to an `AF_UNIX` stream socket in the file system a + connection is made to it and the certificate read from it. + ''; + }; + + TrustedCertificateFile = lib.mkOption { + default = "/etc/ssl/ca/trusted.pem"; + type = lib.types.str; + description = lib.mdDoc '' + A path to a SSL CA certificate file in PEM format, or `all`. + + If `all` is set, then client certificate checking will be + disabled. + + This option can be used with `listen = "https"`. If the path + refers to an `AF_UNIX` stream socket in the file system a + connection is made to it and the certificate read from it. + ''; + }; + }; + }; + }; + }; + + config = lib.mkIf cfg.enable { + systemd.additionalUpstreamSystemUnits = [ + "systemd-journal-remote.service" + "systemd-journal-remote.socket" + ]; + + systemd.services.systemd-journal-remote.serviceConfig.ExecStart = [ + # Clear the default command line + "" + "${pkgs.systemd}/lib/systemd/systemd-journal-remote ${cliArgs}" + ]; + + systemd.sockets.systemd-journal-remote = { + wantedBy = [ "sockets.target" ]; + listenStreams = [ + # Clear the default port + "" + (toString cfg.port) + ]; + }; + + # User and group used by systemd-journal-remote.service + users.groups.systemd-journal-remote = { }; + users.users.systemd-journal-remote = { + isSystemUser = true; + group = "systemd-journal-remote"; + }; + + environment.etc."systemd/journal-remote.conf".source = + format.generate "journal-remote.conf" cfg.settings; + }; +} diff --git a/nixpkgs/nixos/modules/system/boot/systemd/journald-upload.nix b/nixpkgs/nixos/modules/system/boot/systemd/journald-upload.nix new file mode 100644 index 000000000000..6421e5fa486f --- /dev/null +++ b/nixpkgs/nixos/modules/system/boot/systemd/journald-upload.nix @@ -0,0 +1,111 @@ +{ config, lib, pkgs, ... }: + +let + cfg = config.services.journald.upload; + format = pkgs.formats.systemd; +in +{ + meta.maintainers = [ lib.maintainers.raitobezarius ]; + options.services.journald.upload = { + enable = lib.mkEnableOption "uploading the systemd journal to a remote server"; + + settings = lib.mkOption { + default = { }; + + description = lib.mdDoc '' + Configuration for journal-upload. See {manpage}`journal-upload.conf(5)` + for available options. + ''; + + type = lib.types.submodule { + freeformType = format.type; + + options.Upload = { + URL = lib.mkOption { + type = lib.types.str; + example = "https://192.168.1.1"; + description = '' + The URL to upload the journal entries to. + + See the description of `--url=` option in + {manpage}`systemd-journal-upload(8)` for the description of + possible values. + ''; + }; + + ServerKeyFile = lib.mkOption { + type = with lib.types; nullOr str; + example = lib.literalExpression "./server-key.pem"; + # Since systemd-journal-upload uses a DynamicUser, permissions must + # be done using groups + description = '' + SSL key in PEM format. + + In contrary to what the name suggests, this option configures the + client private key sent to the remote journal server. + + This key should not be world-readable, and must be readably by + the `systemd-journal` group. + ''; + default = null; + }; + + ServerCertificateFile = lib.mkOption { + type = with lib.types; nullOr str; + example = lib.literalExpression "./server-ca.pem"; + description = '' + SSL CA certificate in PEM format. + + In contrary to what the name suggests, this option configures the + client certificate sent to the remote journal server. + ''; + default = null; + }; + + TrustedCertificateFile = lib.mkOption { + type = with lib.types; nullOr str; + example = lib.literalExpression "./ca"; + description = '' + SSL CA certificate. + + This certificate will be used to check the remote journal HTTPS + server certificate. + ''; + default = null; + }; + + NetworkTimeoutSec = lib.mkOption { + type = with lib.types; nullOr str; + example = "1s"; + description = '' + When network connectivity to the server is lost, this option + configures the time to wait for the connectivity to get restored. + + If the server is not reachable over the network for the + configured time, `systemd-journal-upload` exits. Takes a value in + seconds (or in other time units if suffixed with "ms", "min", + "h", etc). For details, see {manpage}`systemd.time(5)`. + ''; + default = null; + }; + }; + }; + }; + }; + + config = lib.mkIf cfg.enable { + systemd.additionalUpstreamSystemUnits = [ "systemd-journal-upload.service" ]; + + systemd.services."systemd-journal-upload" = { + wantedBy = [ "multi-user.target" ]; + serviceConfig = { + Restart = "always"; + # To prevent flooding the server in case the server is struggling + RestartSec = "3sec"; + }; + }; + + environment.etc."systemd/journal-upload.conf".source = + format.generate "journal-upload.conf" cfg.settings; + }; +} diff --git a/nixpkgs/nixos/modules/system/boot/systemd/journald.nix b/nixpkgs/nixos/modules/system/boot/systemd/journald.nix new file mode 100644 index 000000000000..62e24a305dde --- /dev/null +++ b/nixpkgs/nixos/modules/system/boot/systemd/journald.nix @@ -0,0 +1,129 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + cfg = config.services.journald; +in { + imports = [ + (mkRenamedOptionModule [ "services" "journald" "enableHttpGateway" ] [ "services" "journald" "gateway" "enable" ]) + ]; + + options = { + services.journald.console = mkOption { + default = ""; + type = types.str; + description = lib.mdDoc "If non-empty, write log messages to the specified TTY device."; + }; + + services.journald.rateLimitInterval = mkOption { + default = "30s"; + type = types.str; + description = lib.mdDoc '' + Configures the rate limiting 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. 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` for important + considerations when setting this value. + ''; + }; + + services.journald.storage = mkOption { + default = "persistent"; + type = types.enum [ "persistent" "volatile" "auto" "none" ]; + description = mdDoc '' + Controls where to store journal data. See + {manpage}`journald.conf(5)` for further information. + ''; + }; + + services.journald.rateLimitBurst = mkOption { + default = 10000; + type = types.int; + description = lib.mdDoc '' + 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 + [ + journald.conf(5)](https://www.freedesktop.org/software/systemd/man/journald.conf.html). + + Note that the total amount of logs stored is limited by journald settings + such as `SystemMaxUse`, which defaults to 10% the file system size + (capped at max 4GB), and `SystemKeepFree`, which defaults to 15% of the + file system size. + + 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. + ''; + }; + + services.journald.extraConfig = mkOption { + default = ""; + type = types.lines; + example = "Storage=volatile"; + description = lib.mdDoc '' + Extra config options for systemd-journald. See man journald.conf + for available options. + ''; + }; + + services.journald.forwardToSyslog = mkOption { + default = config.services.rsyslogd.enable || config.services.syslog-ng.enable; + defaultText = literalExpression "services.rsyslogd.enable || services.syslog-ng.enable"; + type = types.bool; + description = lib.mdDoc '' + Whether to forward log messages to syslog. + ''; + }; + }; + + config = { + systemd.additionalUpstreamSystemUnits = [ + "systemd-journald.socket" + "systemd-journald@.socket" + "systemd-journald-varlink@.socket" + "systemd-journald.service" + "systemd-journald@.service" + "systemd-journal-flush.service" + "systemd-journal-catalog-update.service" + ] ++ (optional (!config.boot.isContainer) "systemd-journald-audit.socket") ++ [ + "systemd-journald-dev-log.socket" + "syslog.socket" + ]; + + environment.etc = { + "systemd/journald.conf".text = '' + [Journal] + Storage=${cfg.storage} + RateLimitInterval=${cfg.rateLimitInterval} + RateLimitBurst=${toString cfg.rateLimitBurst} + ${optionalString (cfg.console != "") '' + ForwardToConsole=yes + TTYPath=${cfg.console} + ''} + ${optionalString (cfg.forwardToSyslog) '' + ForwardToSyslog=yes + ''} + ${cfg.extraConfig} + ''; + }; + + users.groups.systemd-journal.gid = config.ids.gids.systemd-journal; + + systemd.services.systemd-journal-flush.restartIfChanged = false; + systemd.services.systemd-journald.restartTriggers = [ config.environment.etc."systemd/journald.conf".source ]; + systemd.services.systemd-journald.stopIfChanged = false; + systemd.services."systemd-journald@".restartTriggers = [ config.environment.etc."systemd/journald.conf".source ]; + systemd.services."systemd-journald@".stopIfChanged = false; + }; +} diff --git a/nixpkgs/nixos/modules/system/boot/systemd/logind.nix b/nixpkgs/nixos/modules/system/boot/systemd/logind.nix new file mode 100644 index 000000000000..cf01c1882857 --- /dev/null +++ b/nixpkgs/nixos/modules/system/boot/systemd/logind.nix @@ -0,0 +1,205 @@ +{ config, lib, pkgs, utils, ... }: + +with lib; + +let + cfg = config.services.logind; + + logindHandlerType = types.enum [ + "ignore" "poweroff" "reboot" "halt" "kexec" "suspend" + "hibernate" "hybrid-sleep" "suspend-then-hibernate" "lock" + ]; +in +{ + options.services.logind = { + extraConfig = mkOption { + default = ""; + type = types.lines; + example = "IdleAction=lock"; + description = lib.mdDoc '' + Extra config options for systemd-logind. + See [logind.conf(5)](https://www.freedesktop.org/software/systemd/man/logind.conf.html) + for available options. + ''; + }; + + killUserProcesses = mkOption { + default = false; + type = types.bool; + description = lib.mdDoc '' + Specifies whether the processes of a user should be killed + when the user logs out. If true, the scope unit corresponding + to the session and all processes inside that scope will be + terminated. If false, the scope is "abandoned" + (see [systemd.scope(5)](https://www.freedesktop.org/software/systemd/man/systemd.scope.html#)), + and processes are not killed. + + See [logind.conf(5)](https://www.freedesktop.org/software/systemd/man/logind.conf.html#KillUserProcesses=) + for more details. + ''; + }; + + powerKey = mkOption { + default = "poweroff"; + example = "ignore"; + type = logindHandlerType; + + description = lib.mdDoc '' + Specifies what to do when the power key is pressed. + ''; + }; + + powerKeyLongPress = mkOption { + default = "ignore"; + example = "reboot"; + type = logindHandlerType; + + description = lib.mdDoc '' + Specifies what to do when the power key is long-pressed. + ''; + }; + + rebootKey = mkOption { + default = "reboot"; + example = "ignore"; + type = logindHandlerType; + + description = lib.mdDoc '' + Specifies what to do when the reboot key is pressed. + ''; + }; + + rebootKeyLongPress = mkOption { + default = "poweroff"; + example = "ignore"; + type = logindHandlerType; + + description = lib.mdDoc '' + Specifies what to do when the reboot key is long-pressed. + ''; + }; + + suspendKey = mkOption { + default = "suspend"; + example = "ignore"; + type = logindHandlerType; + + description = lib.mdDoc '' + Specifies what to do when the suspend key is pressed. + ''; + }; + + suspendKeyLongPress = mkOption { + default = "hibernate"; + example = "ignore"; + type = logindHandlerType; + + description = lib.mdDoc '' + Specifies what to do when the suspend key is long-pressed. + ''; + }; + + hibernateKey = mkOption { + default = "hibernate"; + example = "ignore"; + type = logindHandlerType; + + description = lib.mdDoc '' + Specifies what to do when the hibernate key is pressed. + ''; + }; + + hibernateKeyLongPress = mkOption { + default = "ignore"; + example = "suspend"; + type = logindHandlerType; + + description = lib.mdDoc '' + Specifies what to do when the hibernate key is long-pressed. + ''; + }; + + lidSwitch = mkOption { + default = "suspend"; + example = "ignore"; + type = logindHandlerType; + + description = lib.mdDoc '' + Specifies what to do when the laptop lid is closed. + ''; + }; + + lidSwitchExternalPower = mkOption { + default = cfg.lidSwitch; + defaultText = literalExpression "services.logind.lidSwitch"; + example = "ignore"; + type = logindHandlerType; + + description = lib.mdDoc '' + Specifies what to do when the laptop lid is closed + and the system is on external power. By default use + the same action as specified in services.logind.lidSwitch. + ''; + }; + + lidSwitchDocked = mkOption { + default = "ignore"; + example = "suspend"; + type = logindHandlerType; + + description = lib.mdDoc '' + Specifies what to do when the laptop lid is closed + and another screen is added. + ''; + }; + }; + + config = { + systemd.additionalUpstreamSystemUnits = [ + "systemd-logind.service" + "autovt@.service" + "systemd-user-sessions.service" + ] ++ optionals config.systemd.package.withImportd [ + "dbus-org.freedesktop.import1.service" + ] ++ optionals config.systemd.package.withMachined [ + "dbus-org.freedesktop.machine1.service" + ] ++ optionals config.systemd.package.withPortabled [ + "dbus-org.freedesktop.portable1.service" + ] ++ [ + "dbus-org.freedesktop.login1.service" + "user@.service" + "user-runtime-dir@.service" + ]; + + environment.etc = { + "systemd/logind.conf".text = '' + [Login] + KillUserProcesses=${if cfg.killUserProcesses then "yes" else "no"} + HandlePowerKey=${cfg.powerKey} + HandlePowerKeyLongPress=${cfg.powerKeyLongPress} + HandleRebootKey=${cfg.rebootKey} + HandleRebootKeyLongPress=${cfg.rebootKeyLongPress} + HandleSuspendKey=${cfg.suspendKey} + HandleSuspendKeyLongPress=${cfg.suspendKeyLongPress} + HandleHibernateKey=${cfg.hibernateKey} + HandleHibernateKeyLongPress=${cfg.hibernateKeyLongPress} + HandleLidSwitch=${cfg.lidSwitch} + HandleLidSwitchExternalPower=${cfg.lidSwitchExternalPower} + HandleLidSwitchDocked=${cfg.lidSwitchDocked} + ${cfg.extraConfig} + ''; + }; + + # Restarting systemd-logind breaks X11 + # - upstream commit: https://cgit.freedesktop.org/xorg/xserver/commit/?id=dc48bd653c7e101 + # - systemd announcement: https://github.com/systemd/systemd/blob/22043e4317ecd2bc7834b48a6d364de76bb26d91/NEWS#L103-L112 + # - this might be addressed in the future by xorg + #systemd.services.systemd-logind.restartTriggers = [ config.environment.etc."systemd/logind.conf".source ]; + systemd.services.systemd-logind.restartIfChanged = false; + systemd.services.systemd-logind.stopIfChanged = false; + + # The user-runtime-dir@ service is managed by systemd-logind we should not touch it or else we break the users' sessions. + systemd.services."user-runtime-dir@".stopIfChanged = false; + systemd.services."user-runtime-dir@".restartIfChanged = false; + }; +} diff --git a/nixpkgs/nixos/modules/system/boot/systemd/nspawn.nix b/nixpkgs/nixos/modules/system/boot/systemd/nspawn.nix new file mode 100644 index 000000000000..b513aa051f28 --- /dev/null +++ b/nixpkgs/nixos/modules/system/boot/systemd/nspawn.nix @@ -0,0 +1,132 @@ +{ config, lib, pkgs, utils, ...}: + +with utils.systemdUtils.unitOptions; +with utils.systemdUtils.lib; +with lib; + +let + cfg = config.systemd.nspawn; + + checkExec = checkUnitConfig "Exec" [ + (assertOnlyFields [ + "Boot" "ProcessTwo" "Parameters" "Environment" "User" "WorkingDirectory" + "PivotRoot" "Capability" "DropCapability" "NoNewPrivileges" "KillSignal" + "Personality" "MachineID" "PrivateUsers" "NotifyReady" "SystemCallFilter" + "LimitCPU" "LimitFSIZE" "LimitDATA" "LimitSTACK" "LimitCORE" "LimitRSS" + "LimitNOFILE" "LimitAS" "LimitNPROC" "LimitMEMLOCK" "LimitLOCKS" + "LimitSIGPENDING" "LimitMSGQUEUE" "LimitNICE" "LimitRTPRIO" "LimitRTTIME" + "OOMScoreAdjust" "CPUAffinity" "Hostname" "ResolvConf" "Timezone" + "LinkJournal" "Ephemeral" "AmbientCapability" + ]) + (assertValueOneOf "Boot" boolValues) + (assertValueOneOf "ProcessTwo" boolValues) + (assertValueOneOf "NotifyReady" boolValues) + ]; + + checkFiles = checkUnitConfig "Files" [ + (assertOnlyFields [ + "ReadOnly" "Volatile" "Bind" "BindReadOnly" "TemporaryFileSystem" + "Overlay" "OverlayReadOnly" "PrivateUsersChown" "BindUser" + "Inaccessible" "PrivateUsersOwnership" + ]) + (assertValueOneOf "ReadOnly" boolValues) + (assertValueOneOf "Volatile" (boolValues ++ [ "state" ])) + (assertValueOneOf "PrivateUsersChown" boolValues) + (assertValueOneOf "PrivateUsersOwnership" [ "off" "chown" "map" "auto" ]) + ]; + + checkNetwork = checkUnitConfig "Network" [ + (assertOnlyFields [ + "Private" "VirtualEthernet" "VirtualEthernetExtra" "Interface" "MACVLAN" + "IPVLAN" "Bridge" "Zone" "Port" + ]) + (assertValueOneOf "Private" boolValues) + (assertValueOneOf "VirtualEthernet" boolValues) + ]; + + instanceOptions = { + options = + (getAttrs [ "enable" ] sharedOptions) + // { + execConfig = mkOption { + default = {}; + example = { Parameters = "/bin/sh"; }; + type = types.addCheck (types.attrsOf unitOption) checkExec; + description = lib.mdDoc '' + Each attribute in this set specifies an option in the + `[Exec]` section of this unit. See + {manpage}`systemd.nspawn(5)` for details. + ''; + }; + + filesConfig = mkOption { + default = {}; + example = { Bind = [ "/home/alice" ]; }; + type = types.addCheck (types.attrsOf unitOption) checkFiles; + description = lib.mdDoc '' + Each attribute in this set specifies an option in the + `[Files]` section of this unit. See + {manpage}`systemd.nspawn(5)` for details. + ''; + }; + + networkConfig = mkOption { + default = {}; + example = { Private = false; }; + type = types.addCheck (types.attrsOf unitOption) checkNetwork; + description = lib.mdDoc '' + Each attribute in this set specifies an option in the + `[Network]` section of this unit. See + {manpage}`systemd.nspawn(5)` for details. + ''; + }; + }; + + }; + + instanceToUnit = name: def: + let base = { + text = '' + [Exec] + ${attrsToSection def.execConfig} + + [Files] + ${attrsToSection def.filesConfig} + + [Network] + ${attrsToSection def.networkConfig} + ''; + } // def; + in base // { unit = makeUnit name base; }; + +in { + + options = { + + systemd.nspawn = mkOption { + default = {}; + type = with types; attrsOf (submodule instanceOptions); + description = lib.mdDoc "Definition of systemd-nspawn configurations."; + }; + + }; + + config = + let + units = mapAttrs' (n: v: let nspawnFile = "${n}.nspawn"; in nameValuePair nspawnFile (instanceToUnit nspawnFile v)) cfg; + in + mkMerge [ + (mkIf (cfg != {}) { + environment.etc."systemd/nspawn".source = mkIf (cfg != {}) (generateUnits { + allowCollisions = false; + type = "nspawn"; + inherit units; + upstreamUnits = []; + upstreamWants = []; + }); + }) + { + systemd.targets.multi-user.wants = [ "machines.target" ]; + } + ]; +} diff --git a/nixpkgs/nixos/modules/system/boot/systemd/oomd.nix b/nixpkgs/nixos/modules/system/boot/systemd/oomd.nix new file mode 100644 index 000000000000..000b18c01609 --- /dev/null +++ b/nixpkgs/nixos/modules/system/boot/systemd/oomd.nix @@ -0,0 +1,71 @@ +{ config, lib, ... }: let + + cfg = config.systemd.oomd; + +in { + imports = [ + (lib.mkRenamedOptionModule [ "systemd" "oomd" "enableUserServices" ] [ "systemd" "oomd" "enableUserSlices" ]) + ]; + + options.systemd.oomd = { + enable = lib.mkEnableOption (lib.mdDoc "the `systemd-oomd` OOM killer") // { default = true; }; + + # Fedora enables the first and third option by default. See the 10-oomd-* files here: + # https://src.fedoraproject.org/rpms/systemd/tree/806c95e1c70af18f81d499b24cd7acfa4c36ffd6 + enableRootSlice = lib.mkEnableOption (lib.mdDoc "oomd on the root slice (`-.slice`)"); + enableSystemSlice = lib.mkEnableOption (lib.mdDoc "oomd on the system slice (`system.slice`)"); + enableUserSlices = lib.mkEnableOption (lib.mdDoc "oomd on all user slices (`user@.slice`) and all user owned slices"); + + extraConfig = lib.mkOption { + type = with lib.types; attrsOf (oneOf [ str int bool ]); + default = {}; + example = lib.literalExpression ''{ DefaultMemoryPressureDurationSec = "20s"; }''; + description = lib.mdDoc '' + Extra config options for `systemd-oomd`. See {command}`man oomd.conf` + for available options. + ''; + }; + }; + + config = lib.mkIf cfg.enable { + systemd.additionalUpstreamSystemUnits = [ + "systemd-oomd.service" + "systemd-oomd.socket" + ]; + systemd.services.systemd-oomd.wantedBy = [ "multi-user.target" ]; + + environment.etc."systemd/oomd.conf".text = lib.generators.toINI {} { + OOM = cfg.extraConfig; + }; + + systemd.oomd.extraConfig.DefaultMemoryPressureDurationSec = lib.mkDefault "20s"; # Fedora default + + users.users.systemd-oom = { + description = "systemd-oomd service user"; + group = "systemd-oom"; + isSystemUser = true; + }; + users.groups.systemd-oom = { }; + + systemd.slices."-".sliceConfig = lib.mkIf cfg.enableRootSlice { + ManagedOOMMemoryPressure = "kill"; + ManagedOOMMemoryPressureLimit = "80%"; + }; + systemd.slices."system".sliceConfig = lib.mkIf cfg.enableSystemSlice { + ManagedOOMMemoryPressure = "kill"; + ManagedOOMMemoryPressureLimit = "80%"; + }; + systemd.slices."user-".sliceConfig = lib.mkIf cfg.enableUserSlices { + ManagedOOMMemoryPressure = "kill"; + ManagedOOMMemoryPressureLimit = "80%"; + }; + systemd.user.units."slice" = lib.mkIf cfg.enableUserSlices { + text = '' + [Slice] + ManagedOOMMemoryPressure=kill + ManagedOOMMemoryPressureLimit=80% + ''; + overrideStrategy = "asDropin"; + }; + }; +} diff --git a/nixpkgs/nixos/modules/system/boot/systemd/repart.nix b/nixpkgs/nixos/modules/system/boot/systemd/repart.nix new file mode 100644 index 000000000000..3be744acd0b3 --- /dev/null +++ b/nixpkgs/nixos/modules/system/boot/systemd/repart.nix @@ -0,0 +1,151 @@ +{ config, lib, pkgs, utils, ... }: + +let + cfg = config.systemd.repart; + initrdCfg = config.boot.initrd.systemd.repart; + + format = pkgs.formats.ini { }; + + definitionsDirectory = utils.systemdUtils.lib.definitions + "repart.d" + format + (lib.mapAttrs (_n: v: { Partition = v; }) cfg.partitions); +in +{ + options = { + boot.initrd.systemd.repart = { + enable = lib.mkEnableOption (lib.mdDoc "systemd-repart") // { + description = lib.mdDoc '' + Grow and add partitions to a partition table at boot time in the initrd. + systemd-repart only works with GPT partition tables. + + To run systemd-repart after the initrd, see + `options.systemd.repart.enable`. + ''; + }; + + device = lib.mkOption { + type = with lib.types; nullOr str; + description = lib.mdDoc '' + The device to operate on. + + If `device == null`, systemd-repart will operate on the device + backing the root partition. So in order to dynamically *create* the + root partition in the initrd you need to set a device. + ''; + default = null; + example = "/dev/vda"; + }; + }; + + systemd.repart = { + enable = lib.mkEnableOption (lib.mdDoc "systemd-repart") // { + description = lib.mdDoc '' + Grow and add partitions to a partition table. + systemd-repart only works with GPT partition tables. + + To run systemd-repart while in the initrd, see + `options.boot.initrd.systemd.repart.enable`. + ''; + }; + + partitions = lib.mkOption { + type = with lib.types; attrsOf (attrsOf (oneOf [ str int bool ])); + default = { }; + example = { + "10-root" = { + Type = "root"; + }; + "20-home" = { + Type = "home"; + SizeMinBytes = "512M"; + SizeMaxBytes = "2G"; + }; + }; + description = lib.mdDoc '' + Specify partitions as a set of the names of the definition files as the + key and the partition configuration as its value. The partition + configuration can use all upstream options. See <link + xlink:href="https://www.freedesktop.org/software/systemd/man/repart.d.html"/> + for all available options. + ''; + }; + }; + }; + + config = lib.mkIf (cfg.enable || initrdCfg.enable) { + assertions = [ + { + assertion = initrdCfg.enable -> config.boot.initrd.systemd.enable; + message = '' + 'boot.initrd.systemd.repart.enable' requires 'boot.initrd.systemd.enable' to be enabled. + ''; + } + ]; + + # systemd-repart uses loopback devices for partition creation + boot.initrd.availableKernelModules = lib.optional initrdCfg.enable "loop"; + + boot.initrd.systemd = lib.mkIf initrdCfg.enable { + additionalUpstreamUnits = [ + "systemd-repart.service" + ]; + + storePaths = [ + "${config.boot.initrd.systemd.package}/bin/systemd-repart" + ]; + + contents."/etc/repart.d".source = definitionsDirectory; + + # Override defaults in upstream unit. + services.systemd-repart = + let + deviceUnit = "${utils.escapeSystemdPath initrdCfg.device}.device"; + in + { + # systemd-repart tries to create directories in /var/tmp by default to + # store large temporary files that benefit from persistence on disk. In + # the initrd, however, /var/tmp does not provide more persistence than + # /tmp, so we re-use it here. + environment."TMPDIR" = "/tmp"; + serviceConfig = { + ExecStart = [ + " " # required to unset the previous value. + # When running in the initrd, systemd-repart by default searches + # for definition files in /sysroot or /sysusr. We tell it to look + # in the initrd itself. + ''${config.boot.initrd.systemd.package}/bin/systemd-repart \ + --definitions=/etc/repart.d \ + --dry-run=no ${lib.optionalString (initrdCfg.device != null) initrdCfg.device} + '' + ]; + }; + # systemd-repart needs to run after /sysroot (or /sysuser, but we + # don't have it) has been mounted because otherwise it cannot + # determine the device (i.e disk) to operate on. If you want to run + # systemd-repart without /sysroot (i.e. to create the root + # partition), you have to explicitly tell it which device to operate + # on. The service then needs to be ordered to run after this device + # is available. + requires = lib.mkIf (initrdCfg.device != null) [ deviceUnit ]; + after = + if initrdCfg.device == null then + [ "sysroot.mount" ] + else + [ deviceUnit ]; + }; + }; + + environment.etc = lib.mkIf cfg.enable { + "repart.d".source = definitionsDirectory; + }; + + systemd = lib.mkIf cfg.enable { + additionalUpstreamSystemUnits = [ + "systemd-repart.service" + ]; + }; + }; + + meta.maintainers = with lib.maintainers; [ nikstur ]; +} diff --git a/nixpkgs/nixos/modules/system/boot/systemd/shutdown.nix b/nixpkgs/nixos/modules/system/boot/systemd/shutdown.nix new file mode 100644 index 000000000000..d7300e940af2 --- /dev/null +++ b/nixpkgs/nixos/modules/system/boot/systemd/shutdown.nix @@ -0,0 +1,66 @@ +{ config, lib, utils, pkgs, ... }: let + + cfg = config.systemd.shutdownRamfs; + + ramfsContents = let + storePaths = map (p: "${p}\n") cfg.storePaths; + contents = lib.mapAttrsToList (_: v: "${v.source}\n${v.target}") (lib.filterAttrs (_: v: v.enable) cfg.contents); + in pkgs.writeText "shutdown-ramfs-contents" (lib.concatStringsSep "\n" (storePaths ++ contents)); + +in { + options.systemd.shutdownRamfs = { + enable = lib.mkEnableOption (lib.mdDoc "pivoting back to an initramfs for shutdown") // { default = true; }; + contents = lib.mkOption { + description = lib.mdDoc "Set of files that have to be linked into the shutdown ramfs"; + example = lib.literalExpression '' + { + "/lib/systemd/system-shutdown/zpool-sync-shutdown".source = writeShellScript "zpool" "exec ''${zfs}/bin/zpool sync" + } + ''; + type = utils.systemdUtils.types.initrdContents; + }; + + storePaths = lib.mkOption { + description = lib.mdDoc '' + Store paths to copy into the shutdown ramfs as well. + ''; + type = lib.types.listOf lib.types.singleLineStr; + default = []; + }; + }; + + config = lib.mkIf cfg.enable { + systemd.shutdownRamfs.contents = { + "/shutdown".source = "${config.systemd.package}/lib/systemd/systemd-shutdown"; + "/etc/initrd-release".source = config.environment.etc.os-release.source; + "/etc/os-release".source = config.environment.etc.os-release.source; + }; + systemd.shutdownRamfs.storePaths = [pkgs.runtimeShell "${pkgs.coreutils}/bin"]; + + systemd.mounts = [{ + what = "tmpfs"; + where = "/run/initramfs"; + type = "tmpfs"; + }]; + + systemd.services.generate-shutdown-ramfs = { + description = "Generate shutdown ramfs"; + wantedBy = [ "shutdown.target" ]; + before = [ "shutdown.target" ]; + unitConfig = { + DefaultDependencies = false; + RequiresMountsFor = "/run/initramfs"; + ConditionFileIsExecutable = [ + "!/run/initramfs/shutdown" + ]; + }; + + serviceConfig = { + Type = "oneshot"; + ProtectSystem = "strict"; + ReadWritePaths = "/run/initramfs"; + ExecStart = "${pkgs.makeInitrdNGTool}/bin/make-initrd-ng ${ramfsContents} /run/initramfs"; + }; + }; + }; +} diff --git a/nixpkgs/nixos/modules/system/boot/systemd/sysupdate.nix b/nixpkgs/nixos/modules/system/boot/systemd/sysupdate.nix new file mode 100644 index 000000000000..1f4088ddf825 --- /dev/null +++ b/nixpkgs/nixos/modules/system/boot/systemd/sysupdate.nix @@ -0,0 +1,136 @@ +{ config, lib, pkgs, utils, ... }: + +let + cfg = config.systemd.sysupdate; + + format = pkgs.formats.ini { listToValue = toString; }; + + definitionsDirectory = utils.systemdUtils.lib.definitions + "sysupdate.d" + format + cfg.transfers; +in +{ + options.systemd.sysupdate = { + + enable = lib.mkEnableOption (lib.mdDoc "systemd-sysupdate") // { + description = lib.mdDoc '' + Atomically update the host OS, container images, portable service + images or other sources. + + If enabled, updates are triggered in regular intervals via a + `systemd.timer` unit. + + Please see + <https://www.freedesktop.org/software/systemd/man/systemd-sysupdate.html> + for more details. + ''; + }; + + timerConfig = utils.systemdUtils.unitOptions.timerOptions.options.timerConfig // { + default = { }; + description = lib.mdDoc '' + The timer configuration for performing the update. + + By default, the upstream configuration is used: + <https://github.com/systemd/systemd/blob/main/units/systemd-sysupdate.timer> + ''; + }; + + reboot = { + enable = lib.mkEnableOption (lib.mdDoc "automatically rebooting after an update") // { + description = lib.mdDoc '' + Whether to automatically reboot after an update. + + If set to `true`, the system will automatically reboot via a + `systemd.timer` unit but only after a new version was installed. + + This uses a unit completely separate from the one performing the + update because it is typically advisable to download updates + regularly while the system is up, but delay reboots until the + appropriate time (i.e. typically at night). + + Set this to `false` if you do not want to reboot after an update. This + is useful when you update a container image or another source where + rebooting is not necessary in order to finalize the update. + ''; + }; + + timerConfig = utils.systemdUtils.unitOptions.timerOptions.options.timerConfig // { + default = { }; + description = lib.mdDoc '' + The timer configuration for rebooting after an update. + + By default, the upstream configuration is used: + <https://github.com/systemd/systemd/blob/main/units/systemd-sysupdate-reboot.timer> + ''; + }; + }; + + transfers = lib.mkOption { + type = with lib.types; attrsOf format.type; + default = { }; + example = { + "10-uki" = { + Transfer = { + ProtectVersion = "%A"; + }; + + Source = { + Type = "url-file"; + Path = "https://download.example.com/"; + MatchPattern = [ "nixos_@v+@l-@d.efi" "nixos_@v+@l.efi" "nixos_@v.efi" ]; + }; + + Target = { + Type = "regular-file"; + Path = "/EFI/Linux"; + PathRelativeTo = "boot"; + MatchPattern = '' + nixos_@v+@l-@d.efi"; \ + nixos_@v+@l.efi \ + nixos_@v.efi + ''; + Mode = "0444"; + TriesLeft = 3; + TriesDone = 0; + InstancesMax = 2; + }; + }; + }; + description = lib.mdDoc '' + Specify transfers as a set of the names of the transfer files as the + key and the configuration as its value. The configuration can use all + upstream options. See + <https://www.freedesktop.org/software/systemd/man/sysupdate.d.html> + for all available options. + ''; + }; + + }; + + config = lib.mkIf cfg.enable { + + systemd.additionalUpstreamSystemUnits = [ + "systemd-sysupdate.service" + "systemd-sysupdate.timer" + "systemd-sysupdate-reboot.service" + "systemd-sysupdate-reboot.timer" + ]; + + systemd.timers = { + "systemd-sysupdate" = { + wantedBy = [ "timers.target" ]; + timerConfig = cfg.timerConfig; + }; + "systemd-sysupdate-reboot" = lib.mkIf cfg.reboot.enable { + wantedBy = [ "timers.target" ]; + timerConfig = cfg.reboot.timerConfig; + }; + }; + + environment.etc."sysupdate.d".source = definitionsDirectory; + }; + + meta.maintainers = with lib.maintainers; [ nikstur ]; +} diff --git a/nixpkgs/nixos/modules/system/boot/systemd/sysusers.nix b/nixpkgs/nixos/modules/system/boot/systemd/sysusers.nix new file mode 100644 index 000000000000..c619c2d91eb0 --- /dev/null +++ b/nixpkgs/nixos/modules/system/boot/systemd/sysusers.nix @@ -0,0 +1,169 @@ +{ config, lib, pkgs, utils, ... }: + +let + + cfg = config.systemd.sysusers; + userCfg = config.users; + + sysusersConfig = pkgs.writeTextDir "00-nixos.conf" '' + # Type Name ID GECOS Home directory Shell + + # Users + ${lib.concatLines (lib.mapAttrsToList + (username: opts: + let + uid = if opts.uid == null then "-" else toString opts.uid; + in + ''u ${username} ${uid}:${opts.group} "${opts.description}" ${opts.home} ${utils.toShellPath opts.shell}'' + ) + userCfg.users) + } + + # Groups + ${lib.concatLines (lib.mapAttrsToList + (groupname: opts: ''g ${groupname} ${if opts.gid == null then "-" else toString opts.gid}'') userCfg.groups) + } + + # Group membership + ${lib.concatStrings (lib.mapAttrsToList + (groupname: opts: (lib.concatMapStrings (username: "m ${username} ${groupname}\n")) opts.members ) userCfg.groups) + } + ''; + + staticSysusersCredentials = pkgs.runCommand "static-sysusers-credentials" { } '' + mkdir $out; cd $out + ${lib.concatLines ( + (lib.mapAttrsToList + (username: opts: "echo -n '${opts.initialHashedPassword}' > 'passwd.hashed-password.${username}'") + (lib.filterAttrs (_username: opts: opts.initialHashedPassword != null) userCfg.users)) + ++ + (lib.mapAttrsToList + (username: opts: "echo -n '${opts.initialPassword}' > 'passwd.plaintext-password.${username}'") + (lib.filterAttrs (_username: opts: opts.initialPassword != null) userCfg.users)) + ++ + (lib.mapAttrsToList + (username: opts: "cat '${opts.hashedPasswordFile}' > 'passwd.hashed-password.${username}'") + (lib.filterAttrs (_username: opts: opts.hashedPasswordFile != null) userCfg.users)) + ) + } + ''; + + staticSysusers = pkgs.runCommand "static-sysusers" + { + nativeBuildInputs = [ pkgs.systemd ]; + } '' + mkdir $out + export CREDENTIALS_DIRECTORY=${staticSysusersCredentials} + systemd-sysusers --root $out ${sysusersConfig}/00-nixos.conf + ''; + +in + +{ + + options = { + + # This module doesn't set it's own user options but reuses the ones from + # users-groups.nix + + systemd.sysusers = { + enable = lib.mkEnableOption (lib.mdDoc "systemd-sysusers") // { + description = lib.mdDoc '' + If enabled, users are created with systemd-sysusers instead of with + the custom `update-users-groups.pl` script. + + Note: This is experimental. + ''; + }; + }; + + }; + + config = lib.mkIf cfg.enable { + + assertions = [ + { + assertion = config.system.activationScripts.users == ""; + message = "system.activationScripts.users has to be empty to use systemd-sysusers"; + } + { + assertion = config.users.mutableUsers -> config.system.etc.overlay.enable; + message = "config.users.mutableUsers requires config.system.etc.overlay.enable."; + } + ]; + + systemd = lib.mkMerge [ + ({ + + # Create home directories, do not create /var/empty even if that's a user's + # home. + tmpfiles.settings.home-directories = lib.mapAttrs' + (username: opts: lib.nameValuePair opts.home { + d = { + mode = opts.homeMode; + user = username; + group = opts.group; + }; + }) + (lib.filterAttrs (_username: opts: opts.home != "/var/empty") userCfg.users); + }) + + (lib.mkIf config.users.mutableUsers { + additionalUpstreamSystemUnits = [ + "systemd-sysusers.service" + ]; + + services.systemd-sysusers = { + # Enable switch-to-configuration to restart the service. + unitConfig.ConditionNeedsUpdate = [ "" ]; + requiredBy = [ "sysinit-reactivation.target" ]; + before = [ "sysinit-reactivation.target" ]; + restartTriggers = [ "${config.environment.etc."sysusers.d".source}" ]; + + serviceConfig = { + LoadCredential = lib.mapAttrsToList + (username: opts: "passwd.hashed-password.${username}:${opts.hashedPasswordFile}") + (lib.filterAttrs (_username: opts: opts.hashedPasswordFile != null) userCfg.users); + SetCredential = (lib.mapAttrsToList + (username: opts: "passwd.hashed-password.${username}:${opts.initialHashedPassword}") + (lib.filterAttrs (_username: opts: opts.initialHashedPassword != null) userCfg.users)) + ++ + (lib.mapAttrsToList + (username: opts: "passwd.plaintext-password.${username}:${opts.initialPassword}") + (lib.filterAttrs (_username: opts: opts.initialPassword != null) userCfg.users)) + ; + }; + }; + }) + ]; + + environment.etc = lib.mkMerge [ + (lib.mkIf (!userCfg.mutableUsers) { + "passwd" = { + source = "${staticSysusers}/etc/passwd"; + mode = "0644"; + }; + "group" = { + source = "${staticSysusers}/etc/group"; + mode = "0644"; + }; + "shadow" = { + source = "${staticSysusers}/etc/shadow"; + mode = "0000"; + }; + "gshadow" = { + source = "${staticSysusers}/etc/gshadow"; + mode = "0000"; + }; + }) + + (lib.mkIf userCfg.mutableUsers { + "sysusers.d".source = sysusersConfig; + }) + ]; + + }; + + meta.maintainers = with lib.maintainers; [ nikstur ]; + +} diff --git a/nixpkgs/nixos/modules/system/boot/systemd/tmpfiles.nix b/nixpkgs/nixos/modules/system/boot/systemd/tmpfiles.nix new file mode 100644 index 000000000000..ee06648f568c --- /dev/null +++ b/nixpkgs/nixos/modules/system/boot/systemd/tmpfiles.nix @@ -0,0 +1,260 @@ +{ config, lib, pkgs, utils, ... }: + +with lib; + +let + cfg = config.systemd.tmpfiles; + systemd = config.systemd.package; +in +{ + options = { + systemd.tmpfiles.rules = mkOption { + type = types.listOf types.str; + default = []; + example = [ "d /tmp 1777 root root 10d" ]; + description = lib.mdDoc '' + Rules for creation, deletion and cleaning of volatile and temporary files + automatically. See + {manpage}`tmpfiles.d(5)` + for the exact format. + ''; + }; + + systemd.tmpfiles.settings = mkOption { + description = lib.mdDoc '' + Declare systemd-tmpfiles rules to create, delete, and clean up volatile + and temporary files and directories. + + Even though the service is called `*tmp*files` you can also create + persistent files. + ''; + example = { + "10-mypackage" = { + "/var/lib/my-service/statefolder".d = { + mode = "0755"; + user = "root"; + group = "root"; + }; + }; + }; + default = {}; + type = types.attrsOf (types.attrsOf (types.attrsOf (types.submodule ({ name, config, ... }: { + options.type = mkOption { + type = types.str; + default = name; + example = "d"; + description = lib.mdDoc '' + The type of operation to perform on the file. + + The type consists of a single letter and optionally one or more + modifier characters. + + Please see the upstream documentation for the available types and + more details: + <https://www.freedesktop.org/software/systemd/man/tmpfiles.d> + ''; + }; + options.mode = mkOption { + type = types.str; + default = "-"; + example = "0755"; + description = lib.mdDoc '' + The file access mode to use when creating this file or directory. + ''; + }; + options.user = mkOption { + type = types.str; + default = "-"; + example = "root"; + description = lib.mdDoc '' + The user of the file. + + This may either be a numeric ID or a user/group name. + + If omitted or when set to `"-"`, the user and group of the user who + invokes systemd-tmpfiles is used. + ''; + }; + options.group = mkOption { + type = types.str; + default = "-"; + example = "root"; + description = lib.mdDoc '' + The group of the file. + + This may either be a numeric ID or a user/group name. + + If omitted or when set to `"-"`, the user and group of the user who + invokes systemd-tmpfiles is used. + ''; + }; + options.age = mkOption { + type = types.str; + default = "-"; + example = "10d"; + description = lib.mdDoc '' + Delete a file when it reaches a certain age. + + If a file or directory is older than the current time minus the age + field, it is deleted. + + If set to `"-"` no automatic clean-up is done. + ''; + }; + options.argument = mkOption { + type = types.str; + default = ""; + example = ""; + description = lib.mdDoc '' + An argument whose meaning depends on the type of operation. + + Please see the upstream documentation for the meaning of this + parameter in different situations: + <https://www.freedesktop.org/software/systemd/man/tmpfiles.d> + ''; + }; + })))); + }; + + systemd.tmpfiles.packages = mkOption { + type = types.listOf types.package; + default = []; + example = literalExpression "[ pkgs.lvm2 ]"; + apply = map getLib; + description = lib.mdDoc '' + List of packages containing {command}`systemd-tmpfiles` rules. + + All files ending in .conf found in + {file}`«pkg»/lib/tmpfiles.d` + will be included. + If this folder does not exist or does not contain any files an error will be returned instead. + + If a {file}`lib` output is available, rules are searched there and only there. + If there is no {file}`lib` output it will fall back to {file}`out` + and if that does not exist either, the default output will be used. + ''; + }; + }; + + config = { + systemd.additionalUpstreamSystemUnits = [ + "systemd-tmpfiles-clean.service" + "systemd-tmpfiles-clean.timer" + "systemd-tmpfiles-setup.service" + "systemd-tmpfiles-setup-dev.service" + ]; + + systemd.additionalUpstreamUserUnits = [ + "systemd-tmpfiles-clean.service" + "systemd-tmpfiles-clean.timer" + "systemd-tmpfiles-setup.service" + ]; + + # Allow systemd-tmpfiles to be restarted by switch-to-configuration. This + # service is not pulled into the normal boot process. It only exists for + # switch-to-configuration. + # + # This needs to be a separate unit because it does not execute + # systemd-tmpfiles with `--boot` as that is supposed to only be executed + # once at boot time. + # + # Keep this aligned with the upstream `systemd-tmpfiles-setup.service` unit. + systemd.services."systemd-tmpfiles-resetup" = { + description = "Re-setup tmpfiles on a system that is already running."; + + requiredBy = [ "sysinit-reactivation.target" ]; + after = [ "local-fs.target" "systemd-sysusers.service" "systemd-journald.service" ]; + before = [ "sysinit-reactivation.target" "shutdown.target" ]; + conflicts = [ "shutdown.target" ]; + restartTriggers = [ config.environment.etc."tmpfiles.d".source ]; + + unitConfig.DefaultDependencies = false; + + serviceConfig = { + Type = "oneshot"; + RemainAfterExit = true; + ExecStart = "systemd-tmpfiles --create --remove --exclude-prefix=/dev"; + SuccessExitStatus = "DATAERR CANTCREAT"; + ImportCredential = [ + "tmpfiles.*" + "loging.motd" + "login.issue" + "network.hosts" + "ssh.authorized_keys.root" + ]; + }; + }; + + environment.etc = { + "tmpfiles.d".source = (pkgs.symlinkJoin { + name = "tmpfiles.d"; + paths = map (p: p + "/lib/tmpfiles.d") cfg.packages; + postBuild = '' + for i in $(cat $pathsPath); do + (test -d "$i" && test $(ls "$i"/*.conf | wc -l) -ge 1) || ( + echo "ERROR: The path '$i' from systemd.tmpfiles.packages contains no *.conf files." + exit 1 + ) + done + '' + concatMapStrings (name: optionalString (hasPrefix "tmpfiles.d/" name) '' + rm -f $out/${removePrefix "tmpfiles.d/" name} + '') config.system.build.etc.passthru.targets; + }) + "/*"; + }; + + systemd.tmpfiles.packages = [ + # Default tmpfiles rules provided by systemd + (pkgs.runCommand "systemd-default-tmpfiles" {} '' + mkdir -p $out/lib/tmpfiles.d + cd $out/lib/tmpfiles.d + + # home.conf creates /srv (which we don't want), and /home, which + # is handled by NixOS anyway. + # ln -s "${systemd}/example/tmpfiles.d/home.conf" + ln -s "${systemd}/example/tmpfiles.d/journal-nocow.conf" + ln -s "${systemd}/example/tmpfiles.d/portables.conf" + ln -s "${systemd}/example/tmpfiles.d/static-nodes-permissions.conf" + ln -s "${systemd}/example/tmpfiles.d/systemd.conf" + ln -s "${systemd}/example/tmpfiles.d/systemd-nologin.conf" + ln -s "${systemd}/example/tmpfiles.d/systemd-nspawn.conf" + ln -s "${systemd}/example/tmpfiles.d/systemd-tmp.conf" + ln -s "${systemd}/example/tmpfiles.d/tmp.conf" + ln -s "${systemd}/example/tmpfiles.d/var.conf" + ln -s "${systemd}/example/tmpfiles.d/x11.conf" + '') + # User-specified tmpfiles rules + (pkgs.writeTextFile { + name = "nixos-tmpfiles.d"; + destination = "/lib/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.rules} + ''; + }) + ] ++ (mapAttrsToList (name: paths: + pkgs.writeTextDir "lib/tmpfiles.d/${name}.conf" (concatStrings (mapAttrsToList (path: types: + concatStrings (mapAttrsToList (_type: entry: '' + '${entry.type}' '${path}' '${entry.mode}' '${entry.user}' '${entry.group}' '${entry.age}' ${entry.argument} + '') types) + ) paths )) + ) cfg.settings); + + systemd.tmpfiles.rules = [ + "d /nix/var 0755 root root - -" + "L+ /nix/var/nix/gcroots/booted-system 0755 root root - /run/booted-system" + "d /run/lock 0755 root root - -" + "d /var/db 0755 root root - -" + "L /etc/mtab - - - - ../proc/mounts" + "L /var/lock - - - - ../run/lock" + # Boot-time cleanup + "R! /etc/group.lock - - - - -" + "R! /etc/passwd.lock - - - - -" + "R! /etc/shadow.lock - - - - -" + "R! /etc/mtab* - - - - -" + "R! /nix/var/nix/gcroots/tmp - - - - -" + "R! /nix/var/nix/temproots - - - - -" + ]; + }; +} diff --git a/nixpkgs/nixos/modules/system/boot/systemd/user.nix b/nixpkgs/nixos/modules/system/boot/systemd/user.nix new file mode 100644 index 000000000000..64dc19633eca --- /dev/null +++ b/nixpkgs/nixos/modules/system/boot/systemd/user.nix @@ -0,0 +1,238 @@ +{ config, lib, pkgs, utils, ... }: +with utils; +with systemdUtils.unitOptions; +with lib; + +let + cfg = config.systemd.user; + + systemd = config.systemd.package; + + inherit + (systemdUtils.lib) + makeUnit + generateUnits + targetToUnit + serviceToUnit + sliceToUnit + socketToUnit + timerToUnit + pathToUnit; + + upstreamUserUnits = [ + "app.slice" + "background.slice" + "basic.target" + "bluetooth.target" + "default.target" + "exit.target" + "graphical-session-pre.target" + "graphical-session.target" + "paths.target" + "printer.target" + "session.slice" + "shutdown.target" + "smartcard.target" + "sockets.target" + "sound.target" + "systemd-exit.service" + "timers.target" + "xdg-desktop-autostart.target" + ] ++ config.systemd.additionalUpstreamUserUnits; + + writeTmpfiles = { rules, user ? null }: + let + suffix = optionalString (user != null) "-${user}"; + in + pkgs.writeTextFile { + name = "nixos-user-tmpfiles.d${suffix}"; + destination = "/etc/xdg/user-tmpfiles.d/00-nixos${suffix}.conf"; + text = '' + # This file is created automatically and should not be modified. + # Please change the options ‘systemd.user.tmpfiles’ instead. + ${concatStringsSep "\n" rules} + ''; + }; +in { + options = { + systemd.user.extraConfig = mkOption { + default = ""; + type = types.lines; + example = "DefaultCPUAccounting=yes"; + description = lib.mdDoc '' + Extra config options for systemd user instances. See {manpage}`systemd-user.conf(5)` for + available options. + ''; + }; + + systemd.user.units = mkOption { + description = lib.mdDoc "Definition of systemd per-user units."; + default = {}; + type = systemdUtils.types.units; + }; + + systemd.user.paths = mkOption { + default = {}; + type = systemdUtils.types.paths; + description = lib.mdDoc "Definition of systemd per-user path units."; + }; + + systemd.user.services = mkOption { + default = {}; + type = systemdUtils.types.services; + description = lib.mdDoc "Definition of systemd per-user service units."; + }; + + systemd.user.slices = mkOption { + default = {}; + type = systemdUtils.types.slices; + description = lib.mdDoc "Definition of systemd per-user slice units."; + }; + + systemd.user.sockets = mkOption { + default = {}; + type = systemdUtils.types.sockets; + description = lib.mdDoc "Definition of systemd per-user socket units."; + }; + + systemd.user.targets = mkOption { + default = {}; + type = systemdUtils.types.targets; + description = lib.mdDoc "Definition of systemd per-user target units."; + }; + + systemd.user.timers = mkOption { + default = {}; + type = systemdUtils.types.timers; + description = lib.mdDoc "Definition of systemd per-user timer units."; + }; + + systemd.user.tmpfiles = { + rules = mkOption { + type = types.listOf types.str; + default = []; + example = [ "D %C - - - 7d" ]; + description = lib.mdDoc '' + Global user rules for creation, deletion and cleaning of volatile and + temporary files automatically. See + {manpage}`tmpfiles.d(5)` + for the exact format. + ''; + }; + + users = mkOption { + description = mdDoc '' + Per-user rules for creation, deletion and cleaning of volatile and + temporary files automatically. + ''; + default = {}; + type = types.attrsOf (types.submodule { + options = { + rules = mkOption { + type = types.listOf types.str; + default = []; + example = [ "D %C - - - 7d" ]; + description = mdDoc '' + Per-user rules for creation, deletion and cleaning of volatile and + temporary files automatically. See + {manpage}`tmpfiles.d(5)` + for the exact format. + ''; + }; + }; + }); + }; + }; + + systemd.additionalUpstreamUserUnits = mkOption { + default = []; + type = types.listOf types.str; + example = []; + description = lib.mdDoc '' + Additional units shipped with systemd that should be enabled for per-user systemd instances. + ''; + internal = true; + }; + }; + + config = { + systemd.additionalUpstreamSystemUnits = [ + "user.slice" + ]; + + environment.etc = { + "systemd/user".source = generateUnits { + type = "user"; + inherit (cfg) units; + upstreamUnits = upstreamUserUnits; + upstreamWants = []; + }; + + "systemd/user.conf".text = '' + [Manager] + ${cfg.extraConfig} + ''; + }; + + systemd.user.units = + mapAttrs' (n: v: nameValuePair "${n}.path" (pathToUnit n v)) cfg.paths + // mapAttrs' (n: v: nameValuePair "${n}.service" (serviceToUnit n v)) cfg.services + // mapAttrs' (n: v: nameValuePair "${n}.slice" (sliceToUnit n v)) cfg.slices + // mapAttrs' (n: v: nameValuePair "${n}.socket" (socketToUnit n v)) cfg.sockets + // mapAttrs' (n: v: nameValuePair "${n}.target" (targetToUnit n v)) cfg.targets + // mapAttrs' (n: v: nameValuePair "${n}.timer" (timerToUnit n v)) cfg.timers; + + # Generate timer units for all services that have a ‘startAt’ value. + systemd.user.timers = + mapAttrs (name: service: { + wantedBy = ["timers.target"]; + timerConfig.OnCalendar = service.startAt; + }) + (filterAttrs (name: service: service.startAt != []) cfg.services); + + # Provide the systemd-user PAM service, required to run systemd + # user instances. + security.pam.services.systemd-user = + { # Ensure that pam_systemd gets included. This is special-cased + # in systemd to provide XDG_RUNTIME_DIR. + startSession = true; + # Disable pam_mount in systemd-user to prevent it from being called + # multiple times during login, because it will prevent pam_mount from + # unmounting the previously mounted volumes. + pamMount = false; + }; + + # Some overrides to upstream units. + systemd.services."user@".restartIfChanged = false; + systemd.services.systemd-user-sessions.restartIfChanged = false; # Restart kills all active sessions. + + # enable systemd user tmpfiles + systemd.user.services.systemd-tmpfiles-setup.wantedBy = + optional + (cfg.tmpfiles.rules != [] || any (cfg': cfg'.rules != []) (attrValues cfg.tmpfiles.users)) + "basic.target"; + + # /run/current-system/sw/etc/xdg is in systemd's $XDG_CONFIG_DIRS so we can + # write the tmpfiles.d rules for everyone there + environment.systemPackages = + optional + (cfg.tmpfiles.rules != []) + (writeTmpfiles { inherit (cfg.tmpfiles) rules; }); + + # /etc/profiles/per-user/$USER/etc/xdg is in systemd's $XDG_CONFIG_DIRS so + # we can write a single user's tmpfiles.d rules there + users.users = + mapAttrs + (user: cfg': { + packages = optional (cfg'.rules != []) (writeTmpfiles { + inherit (cfg') rules; + inherit user; + }); + }) + cfg.tmpfiles.users; + + system.userActivationScripts.tmpfiles = '' + ${config.systemd.package}/bin/systemd-tmpfiles --user --create --remove + ''; + }; +} diff --git a/nixpkgs/nixos/modules/system/boot/systemd/userdbd.nix b/nixpkgs/nixos/modules/system/boot/systemd/userdbd.nix new file mode 100644 index 000000000000..e7f6d42341c4 --- /dev/null +++ b/nixpkgs/nixos/modules/system/boot/systemd/userdbd.nix @@ -0,0 +1,18 @@ +{ config, lib, ... }: + +let + cfg = config.services.userdbd; +in +{ + options.services.userdbd.enable = lib.mkEnableOption (lib.mdDoc '' + the systemd JSON user/group record lookup service + ''); + config = lib.mkIf cfg.enable { + systemd.additionalUpstreamSystemUnits = [ + "systemd-userdbd.socket" + "systemd-userdbd.service" + ]; + + systemd.sockets.systemd-userdbd.wantedBy = [ "sockets.target" ]; + }; +} diff --git a/nixpkgs/nixos/modules/system/boot/timesyncd.nix b/nixpkgs/nixos/modules/system/boot/timesyncd.nix new file mode 100644 index 000000000000..2666e4cd6b28 --- /dev/null +++ b/nixpkgs/nixos/modules/system/boot/timesyncd.nix @@ -0,0 +1,92 @@ +{ config, lib, ... }: + +with lib; + +{ + + options = { + + services.timesyncd = { + enable = mkOption { + default = !config.boot.isContainer; + defaultText = literalExpression "!config.boot.isContainer"; + type = types.bool; + description = lib.mdDoc '' + Enables the systemd NTP client daemon. + ''; + }; + servers = mkOption { + default = config.networking.timeServers; + defaultText = literalExpression "config.networking.timeServers"; + type = types.listOf types.str; + description = lib.mdDoc '' + The set of NTP servers from which to synchronise. + ''; + }; + extraConfig = mkOption { + default = ""; + type = types.lines; + example = '' + PollIntervalMaxSec=180 + ''; + description = lib.mdDoc '' + Extra config options for systemd-timesyncd. See + [ + timesyncd.conf(5)](https://www.freedesktop.org/software/systemd/man/timesyncd.conf.html) for available options. + ''; + }; + }; + }; + + config = mkIf config.services.timesyncd.enable { + + systemd.additionalUpstreamSystemUnits = [ "systemd-timesyncd.service" ]; + + systemd.services.systemd-timesyncd = { + wantedBy = [ "sysinit.target" ]; + aliases = [ "dbus-org.freedesktop.timesync1.service" ]; + restartTriggers = [ config.environment.etc."systemd/timesyncd.conf".source ]; + # systemd-timesyncd disables DNSSEC validation in the nss-resolve module by setting SYSTEMD_NSS_RESOLVE_VALIDATE to 0 in the unit file. + # This is required in order to solve the chicken-and-egg problem when DNSSEC validation needs the correct time to work, but to set the + # correct time, we need to connect to an NTP server, which usually requires resolving its hostname. + # In order for nss-resolve to be able to read this environment variable we patch systemd-timesyncd to disable NSCD and use NSS modules directly. + # This means that systemd-timesyncd needs to have NSS modules path in LD_LIBRARY_PATH. When systemd-resolved is disabled we still need to set + # NSS module path so that systemd-timesyncd keeps using other NSS modules that are configured in the system. + environment.LD_LIBRARY_PATH = config.system.nssModules.path; + + preStart = ( + # Ensure that we have some stored time to prevent + # systemd-timesyncd to resort back to the fallback time. If + # the file doesn't exist we assume that our current system + # clock is good enough to provide an initial value. + '' + if ! [ -f /var/lib/systemd/timesync/clock ]; then + test -d /var/lib/systemd/timesync || mkdir -p /var/lib/systemd/timesync + touch /var/lib/systemd/timesync/clock + fi + '' + + # workaround an issue of systemd-timesyncd not starting due to upstream systemd reverting their dynamic users changes + # - https://github.com/NixOS/nixpkgs/pull/61321#issuecomment-492423742 + # - https://github.com/systemd/systemd/issues/12131 + (lib.optionalString (versionOlder config.system.stateVersion "19.09") '' + if [ -L /var/lib/systemd/timesync ]; then + rm /var/lib/systemd/timesync + mv /var/lib/private/systemd/timesync /var/lib/systemd/timesync + fi + '') + ); + }; + + environment.etc."systemd/timesyncd.conf".text = '' + [Time] + NTP=${concatStringsSep " " config.services.timesyncd.servers} + ${config.services.timesyncd.extraConfig} + ''; + + users.users.systemd-timesync = { + uid = config.ids.uids.systemd-timesync; + group = "systemd-timesync"; + }; + users.groups.systemd-timesync.gid = config.ids.gids.systemd-timesync; + }; +} diff --git a/nixpkgs/nixos/modules/system/boot/tmp.nix b/nixpkgs/nixos/modules/system/boot/tmp.nix new file mode 100644 index 000000000000..fd16cd3fba42 --- /dev/null +++ b/nixpkgs/nixos/modules/system/boot/tmp.nix @@ -0,0 +1,69 @@ +{ config, lib, ... }: + +with lib; + +let + cfg = config.boot.tmp; +in +{ + imports = [ + (mkRenamedOptionModule [ "boot" "cleanTmpDir" ] [ "boot" "tmp" "cleanOnBoot" ]) + (mkRenamedOptionModule [ "boot" "tmpOnTmpfs" ] [ "boot" "tmp" "useTmpfs" ]) + (mkRenamedOptionModule [ "boot" "tmpOnTmpfsSize" ] [ "boot" "tmp" "tmpfsSize" ]) + ]; + + options = { + boot.tmp = { + cleanOnBoot = mkOption { + type = types.bool; + default = false; + description = lib.mdDoc '' + Whether to delete all files in {file}`/tmp` during boot. + ''; + }; + + tmpfsSize = mkOption { + type = types.oneOf [ types.str types.types.ints.positive ]; + default = "50%"; + description = lib.mdDoc '' + Size of tmpfs in percentage. + Percentage is defined by systemd. + ''; + }; + + useTmpfs = mkOption { + type = types.bool; + default = false; + description = lib.mdDoc '' + Whether to mount a tmpfs on {file}`/tmp` during boot. + + ::: {.note} + Large Nix builds can fail if the mounted tmpfs is not large enough. + In such a case either increase the tmpfsSize or disable this option. + ::: + ''; + }; + }; + }; + + config = { + # When changing remember to update /tmp mount in virtualisation/qemu-vm.nix + systemd.mounts = mkIf cfg.useTmpfs [ + { + what = "tmpfs"; + where = "/tmp"; + type = "tmpfs"; + mountConfig.Options = concatStringsSep "," [ + "mode=1777" + "strictatime" + "rw" + "nosuid" + "nodev" + "size=${toString cfg.tmpfsSize}" + ]; + } + ]; + + systemd.tmpfiles.rules = optional cfg.cleanOnBoot "D! /tmp 1777 root root"; + }; +} diff --git a/nixpkgs/nixos/modules/system/boot/uki.nix b/nixpkgs/nixos/modules/system/boot/uki.nix new file mode 100644 index 000000000000..63a7cbc5967b --- /dev/null +++ b/nixpkgs/nixos/modules/system/boot/uki.nix @@ -0,0 +1,85 @@ +{ config, lib, pkgs, ... }: + +let + + cfg = config.boot.uki; + + inherit (pkgs.stdenv.hostPlatform) efiArch; + + format = pkgs.formats.ini { }; + ukifyConfig = format.generate "ukify.conf" cfg.settings; + +in + +{ + options = { + + boot.uki = { + name = lib.mkOption { + type = lib.types.str; + description = lib.mdDoc "Name of the UKI"; + }; + + version = lib.mkOption { + type = lib.types.nullOr lib.types.str; + default = config.system.image.version; + defaultText = lib.literalExpression "config.system.image.version"; + description = lib.mdDoc "Version of the image or generation the UKI belongs to"; + }; + + settings = lib.mkOption { + type = format.type; + description = lib.mdDoc '' + The configuration settings for ukify. These control what the UKI + contains and how it is built. + ''; + }; + }; + + system.boot.loader.ukiFile = lib.mkOption { + type = lib.types.str; + internal = true; + description = lib.mdDoc "Name of the UKI file"; + }; + + }; + + config = { + + boot.uki.name = lib.mkOptionDefault (if config.system.image.id != null then + config.system.image.id + else + "nixos"); + + boot.uki.settings = { + UKI = { + Linux = lib.mkOptionDefault "${config.boot.kernelPackages.kernel}/${config.system.boot.loader.kernelFile}"; + Initrd = lib.mkOptionDefault "${config.system.build.initialRamdisk}/${config.system.boot.loader.initrdFile}"; + Cmdline = lib.mkOptionDefault "init=${config.system.build.toplevel}/init ${toString config.boot.kernelParams}"; + Stub = lib.mkOptionDefault "${pkgs.systemd}/lib/systemd/boot/efi/linux${efiArch}.efi.stub"; + Uname = lib.mkOptionDefault "${config.boot.kernelPackages.kernel.modDirVersion}"; + OSRelease = lib.mkOptionDefault "@${config.system.build.etc}/etc/os-release"; + # This is needed for cross compiling. + EFIArch = lib.mkOptionDefault efiArch; + }; + }; + + system.boot.loader.ukiFile = + let + name = config.boot.uki.name; + version = config.boot.uki.version; + versionInfix = if version != null then "_${version}" else ""; + in + name + versionInfix + ".efi"; + + system.build.uki = pkgs.runCommand config.system.boot.loader.ukiFile { } '' + mkdir -p $out + ${pkgs.buildPackages.systemdUkify}/lib/systemd/ukify build \ + --config=${ukifyConfig} \ + --output="$out/${config.system.boot.loader.ukiFile}" + ''; + + meta.maintainers = with lib.maintainers; [ nikstur ]; + + }; +} diff --git a/nixpkgs/nixos/modules/system/boot/unl0kr.nix b/nixpkgs/nixos/modules/system/boot/unl0kr.nix new file mode 100644 index 000000000000..8d9af37382e0 --- /dev/null +++ b/nixpkgs/nixos/modules/system/boot/unl0kr.nix @@ -0,0 +1,89 @@ +{ config, lib, pkgs, ... }: + +let + cfg = config.boot.initrd.unl0kr; +in +{ + options.boot.initrd.unl0kr = { + enable = lib.mkEnableOption (lib.mdDoc "unl0kr in initrd") // { + description = lib.mdDoc '' + Whether to enable the unl0kr on-screen keyboard in initrd to unlock LUKS. + ''; + }; + }; + + config = lib.mkIf cfg.enable { + meta.maintainers = with lib.maintainers; [ tomfitzhenry ]; + assertions = [ + { + assertion = cfg.enable -> config.boot.initrd.systemd.enable; + message = "boot.initrd.unl0kr is only supported with boot.initrd.systemd."; + } + ]; + + boot.initrd.systemd = { + storePaths = with pkgs; [ + "${pkgs.gnugrep}/bin/grep" + libinput + xkeyboard_config + "${config.boot.initrd.systemd.package}/lib/systemd/systemd-reply-password" + "${pkgs.unl0kr}/bin/unl0kr" + ]; + services = { + unl0kr-ask-password = { + description = "Forward Password Requests to unl0kr"; + conflicts = [ + "emergency.service" + "initrd-switch-root.target" + "shutdown.target" + ]; + unitConfig.DefaultDependencies = false; + after = [ + "systemd-vconsole-setup.service" + "udev.service" + ]; + before = [ + "shutdown.target" + ]; + script = '' + # This script acts as a Password Agent: https://systemd.io/PASSWORD_AGENTS/ + + DIR=/run/systemd/ask-password/ + # If a user has multiple encrypted disks, the requests might come in different times, + # so make sure to answer as many requests as we can. Once boot succeeds, other + # password agents will be responsible for watching for requests. + while [ -d $DIR ] && [ "$(ls -A $DIR/ask.*)" ]; + do + for file in `ls $DIR/ask.*`; do + socket="$(cat "$file" | ${pkgs.gnugrep}/bin/grep "Socket=" | cut -d= -f2)" + ${pkgs.unl0kr}/bin/unl0kr | ${config.boot.initrd.systemd.package}/lib/systemd/systemd-reply-password 1 "$socket" + done + done + ''; + }; + }; + + paths = { + unl0kr-ask-password = { + description = "Forward Password Requests to unl0kr"; + conflicts = [ + "emergency.service" + "initrd-switch-root.target" + "shutdown.target" + ]; + unitConfig.DefaultDependencies = false; + before = [ + "shutdown.target" + "paths.target" + "cryptsetup.target" + ]; + wantedBy = [ "sysinit.target" ]; + pathConfig = { + DirectoryNotEmpty = "/run/systemd/ask-password"; + MakeDirectory = true; + }; + }; + }; + }; + }; +} diff --git a/nixpkgs/nixos/modules/system/boot/uvesafb.nix b/nixpkgs/nixos/modules/system/boot/uvesafb.nix new file mode 100644 index 000000000000..b10dc42887a1 --- /dev/null +++ b/nixpkgs/nixos/modules/system/boot/uvesafb.nix @@ -0,0 +1,39 @@ +{ config, lib, pkgs, ... }: +let + cfg = config.boot.uvesafb; + inherit (lib) mkIf mkEnableOption mkOption mdDoc types; +in { + options = { + boot.uvesafb = { + enable = mkEnableOption (mdDoc "uvesafb"); + + gfx-mode = mkOption { + type = types.str; + default = "1024x768-32"; + description = mdDoc "Screen resolution in modedb format. See [uvesafb](https://docs.kernel.org/fb/uvesafb.html) and [modedb](https://docs.kernel.org/fb/modedb.html) documentation for more details. The default value is a sensible default but may be not ideal for all setups."; + }; + + v86d.package = mkOption { + type = types.package; + description = mdDoc "Which v86d package to use with uvesafb"; + defaultText = ''config.boot.kernelPackages.v86d.overrideAttrs (old: { + hardeningDisable = [ "all" ]; + })''; + default = config.boot.kernelPackages.v86d.overrideAttrs (old: { + hardeningDisable = [ "all" ]; + }); + }; + }; + }; + config = mkIf cfg.enable { + boot.initrd = { + kernelModules = [ "uvesafb" ]; + extraFiles."/usr/v86d".source = cfg.v86d.package; + }; + + boot.kernelParams = [ + "video=uvesafb:mode:${cfg.gfx-mode},mtrr:3,ywrap" + ''uvesafb.v86d="${cfg.v86d.package}/bin/v86d"'' + ]; + }; +} |