diff options
Diffstat (limited to 'nixpkgs/nixos/modules/system/boot')
28 files changed, 1605 insertions, 707 deletions
diff --git a/nixpkgs/nixos/modules/system/boot/emergency-mode.nix b/nixpkgs/nixos/modules/system/boot/emergency-mode.nix index 9cdab841619..ec697bcee26 100644 --- a/nixpkgs/nixos/modules/system/boot/emergency-mode.nix +++ b/nixpkgs/nixos/modules/system/boot/emergency-mode.nix @@ -34,4 +34,4 @@ with lib; }; -}
\ No newline at end of file +} diff --git a/nixpkgs/nixos/modules/system/boot/initrd-network.nix b/nixpkgs/nixos/modules/system/boot/initrd-network.nix index 0ab6e626b34..ec794d6eb01 100644 --- a/nixpkgs/nixos/modules/system/boot/initrd-network.nix +++ b/nixpkgs/nixos/modules/system/boot/initrd-network.nix @@ -139,7 +139,7 @@ in boot.initrd.postMountCommands = mkIf cfg.flushBeforeStage2 '' for iface in $ifaces; do ip address flush "$iface" - ip link down "$iface" + ip link set "$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 00000000000..e59bc7b6678 --- /dev/null +++ b/nixpkgs/nixos/modules/system/boot/initrd-openvpn.nix @@ -0,0 +1,81 @@ +{ 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 = '' + 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</option> 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 = '' + The configuration file for OpenVPN. + + <warning> + <para> + Unless your bootloader supports initrd secrets, this configuration + is stored insecurely in the global Nix store. + </para> + </warning> + ''; + example = "./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 = '' + copy_bin_and_libs ${pkgs.openvpn}/bin/openvpn + copy_bin_and_libs ${pkgs.iproute}/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.secrets = { + "/etc/initrd.ovpn" = cfg.configuration; + }; + + # openvpn --version would exit with 1 instead of 0 + boot.initrd.extraUtilsCommandsTest = '' + $out/bin/openvpn --show-gateway + ''; + + # Add `iproute /bin/ip` to the config, to ensure that openvpn + # is able to set the routes + boot.initrd.network.postCommands = '' + (cat /etc/initrd.ovpn; echo -e '\niproute /bin/ip') | \ + openvpn /dev/stdin & + ''; + }; + +} diff --git a/nixpkgs/nixos/modules/system/boot/kernel_config.nix b/nixpkgs/nixos/modules/system/boot/kernel_config.nix index a316782dfc5..783685c9dfe 100644 --- a/nixpkgs/nixos/modules/system/boot/kernel_config.nix +++ b/nixpkgs/nixos/modules/system/boot/kernel_config.nix @@ -22,7 +22,7 @@ let mergeFalseByDefault = locs: defs: if defs == [] then abort "This case should never happen." - else if any (x: x == false) defs then false + else if any (x: x == false) (getValues defs) then false else true; kernelItem = types.submodule { @@ -54,7 +54,8 @@ let type = types.bool // { merge = mergeFalseByDefault; }; default = false; description = '' - Wether option should generate a failure when unused. + Whether option should generate a failure when unused. + Upon merging values, mandatory wins over optional. ''; }; }; @@ -121,7 +122,7 @@ in type = types.attrsOf kernelItem; example = literalExample '' with lib.kernel; { "9P_NET" = yes; - USB = optional yes; + USB = option yes; MMC_BLOCK_MINORS = freeform "32"; }''; description = '' 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 index e723b9eb7cb..8ae23dc988c 100644 --- 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 @@ -63,7 +63,7 @@ addEntry() { 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 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 index af39c7bb684..bd508bbe8ea 100644 --- a/nixpkgs/nixos/modules/system/boot/loader/generic-extlinux-compatible/default.nix +++ b/nixpkgs/nixos/modules/system/boot/loader/generic-extlinux-compatible/default.nix @@ -4,11 +4,15 @@ 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 = { @@ -34,11 +38,28 @@ in Maximum number of configurations in the boot menu. ''; }; + + populateCmd = mkOption { + type = types.str; + readOnly = true; + description = '' + Contains the builder command used to populate an image, + honoring all options except the <literal>-c <path-to-default-configuration></literal> + argument. + Useful to have for sdImage.populateRootCommands + ''; + }; + }; }; - config = mkIf cfg.enable { - system.build.installBootLoader = "${builder} -g ${toString cfg.configurationLimit} -t ${timeoutStr} -c"; - system.boot.loader.id = "generic-extlinux-compatible"; - }; + config = let + builderArgs = "-g ${toString cfg.configurationLimit} -t ${timeoutStr}" + lib.optionalString (dtCfg.name != null) " -n ${dtCfg.name}"; + 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.sh b/nixpkgs/nixos/modules/system/boot/loader/generic-extlinux-compatible/extlinux-conf-builder.sh index 0092ee92b62..854684b87fa 100644 --- 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 @@ -6,7 +6,7 @@ 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>]" >&2 + echo "usage: $0 -t <timeout> -c <path-to-default-configuration> [-d <boot-dir>] [-g <num-generations>] [-n <dtbName>]" >&2 exit 1 } @@ -15,7 +15,7 @@ default= # Default configuration target=/boot # Target directory numGenerations=0 # Number of other generations to include in the menu -while getopts "t:c:d:g:" opt; do +while getopts "t:c:d:g:n:" opt; do case "$opt" in t) # U-Boot interprets '0' as infinite and negative as instant boot if [ "$OPTARG" -lt 0 ]; then @@ -29,6 +29,7 @@ while getopts "t:c:d:g:" opt; do c) default="$OPTARG" ;; d) target="$OPTARG" ;; g) numGenerations="$OPTARG" ;; + n) dtbName="$OPTARG" ;; \?) usage ;; esac done @@ -96,7 +97,17 @@ addEntry() { echo " LINUX ../nixos/$(basename $kernel)" echo " INITRD ../nixos/$(basename $initrd)" if [ -d "$dtbDir" ]; then - echo " FDTDIR ../nixos/$(basename $dtbs)" + # 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 echo " APPEND systemConfig=$path init=$path/init $extraParams" } diff --git a/nixpkgs/nixos/modules/system/boot/loader/grub/grub.nix b/nixpkgs/nixos/modules/system/boot/loader/grub/grub.nix index c775632a4aa..20e39628eab 100644 --- a/nixpkgs/nixos/modules/system/boot/loader/grub/grub.nix +++ b/nixpkgs/nixos/modules/system/boot/loader/grub/grub.nix @@ -55,12 +55,15 @@ let storePath = config.boot.loader.grub.storePath; bootloaderId = if args.efiBootloaderId == null then "NixOS${efiSysMountPoint'}" else args.efiBootloaderId; timeout = if config.boot.loader.timeout == null then -1 else config.boot.loader.timeout; + users = if cfg.users == {} || cfg.version != 1 then cfg.users else throw "GRUB version 1 does not support user accounts."; + theme = f cfg.theme; inherit efiSysMountPoint; inherit (args) devices; inherit (efi) canTouchEfiVariables; inherit (cfg) version extraConfig extraPerEntryConfig extraEntries forceInstall useOSProber - extraEntriesBeforeNixOS extraPrepareConfig extraInitrd configurationLimit copyKernels + extraGrubInstallArgs + extraEntriesBeforeNixOS extraPrepareConfig configurationLimit copyKernels default fsIdentifier efiSupport efiInstallAsRemovable gfxmodeEfi gfxmodeBios gfxpayloadEfi gfxpayloadBios; path = with pkgs; makeBinPath ( [ coreutils gnused gnugrep findutils diffutils btrfs-progs utillinux mdadm ] @@ -83,7 +86,7 @@ let ] ++ (optional (cfg.fontSize!=null) "--size ${toString cfg.fontSize}"))) ); - defaultSplash = "${pkgs.nixos-artwork.wallpapers.simple-dark-gray-bootloader}/share/artwork/gnome/nix-wallpaper-simple-dark-gray_bootloader.png"; + defaultSplash = pkgs.nixos-artwork.wallpapers.simple-dark-gray-bootloader.gnomeFilePath; in { @@ -137,6 +140,67 @@ in ''; }; + users = mkOption { + default = {}; + example = { + root = { hashedPasswordFile = "/path/to/file"; }; + }; + description = '' + 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 = with types; attrsOf (submodule { + options = { + hashedPasswordFile = mkOption { + example = "/path/to/file"; + default = null; + type = with types; uniq (nullOr str); + description = '' + 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 = '' + 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 = '' + 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 = '' + 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 = [ @@ -236,6 +300,33 @@ in ''; }; + extraGrubInstallArgs = mkOption { + default = [ ]; + example = [ "--modules=nativedisk ahci pata part_gpt part_msdos diskfilter mdraid1x lvm ext2" ]; + type = types.listOf types.str; + description = '' + Additional arguments passed to <literal>grub-install</literal>. + + 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 <literal>grub rescue</literal> 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 + <literal>nativedisk</literal> 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 + <link xlink:href="http://git.savannah.gnu.org/cgit/grub.git/tree/grub-core/commands/nativedisk.c?h=grub-2.04#n326"> + GRUB source code + </link> + for which disk modules are available. + + The list elements are passed directly as <literal>argv</literal> + arguments to the <literal>grub-install</literal> program, in order. + ''; + }; + extraPerEntryConfig = mkOption { default = ""; example = "root (hd0)"; @@ -292,19 +383,6 @@ in ''; }; - extraInitrd = mkOption { - type = types.nullOr types.path; - default = null; - example = "/boot/extra_initramfs.gz"; - description = '' - The path to a second initramfs to be supplied to the kernel. - This ramfs will not be copied to the store, so that it can - contain secrets such as LUKS keyfiles or ssh keys. - This implies that rolling back to a previous configuration - won't rollback the state of this file. - ''; - }; - useOSProber = mkOption { default = false; type = types.bool; @@ -349,6 +427,19 @@ in ''; }; + theme = mkOption { + type = types.nullOr types.path; + example = literalExample "pkgs.nixos-grub2-theme"; + default = null; + description = '' + Grub theme to be used. + + <note><para> + This options has no effect for GRUB 1. + </para></note> + ''; + }; + splashMode = mkOption { type = types.enum [ "normal" "stretch" ]; default = "stretch"; @@ -608,6 +699,8 @@ in { path = "/boot"; inherit (cfg) devices; inherit (efi) efiSysMountPoint; } ]; + boot.loader.supportsInitrdSecrets = true; + system.build.installBootLoader = let install-grub-pl = pkgs.substituteAll { @@ -618,7 +711,7 @@ in in pkgs.writeScript "install-grub.sh" ('' #!${pkgs.runtimeShell} set -e - export PERL5LIB=${with pkgs.perlPackages; makePerlPath [ FileSlurp XMLLibXML XMLSAX XMLSAXBase ListCompare ]} + export PERL5LIB=${with pkgs.perlPackages; makePerlPath [ FileSlurp FileCopyRecursive XMLLibXML XMLSAX XMLSAXBase ListCompare JSON ]} ${optionalString cfg.enableCryptodisk "export GRUB_ENABLE_CRYPTODISK=y"} '' + flip concatMapStrings cfg.mirroredBoots (args: '' ${pkgs.perl}/bin/perl ${install-grub-pl} ${grubConfig args} $@ @@ -705,6 +798,24 @@ in (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" "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 index 8df18cbd901..59f5638044f 100644 --- a/nixpkgs/nixos/modules/system/boot/loader/grub/install-grub.pl +++ b/nixpkgs/nixos/modules/system/boot/loader/grub/install-grub.pl @@ -6,8 +6,11 @@ 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; @@ -20,6 +23,16 @@ 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) = @_; local $/ = undef; open FILE, "<$fn" or return undef; my $s = <FILE>; close FILE; @@ -49,7 +62,6 @@ my $extraPrepareConfig = get("extraPrepareConfig"); my $extraPerEntryConfig = get("extraPerEntryConfig"); my $extraEntries = get("extraEntries"); my $extraEntriesBeforeNixOS = get("extraEntriesBeforeNixOS") eq "true"; -my $extraInitrd = get("extraInitrd"); my $splashImage = get("splashImage"); my $splashMode = get("splashMode"); my $backgroundColor = get("backgroundColor"); @@ -72,6 +84,7 @@ my $gfxpayloadBios = get("gfxpayloadBios"); my $bootloaderId = get("bootloaderId"); my $forceInstall = get("forceInstall"); my $font = get("font"); +my $theme = get("theme"); $ENV{'PATH'} = get("path"); die "unsupported GRUB version\n" if $grubVersion != 1 && $grubVersion != 2; @@ -232,13 +245,6 @@ my $grubStore; if ($copyKernels == 0) { $grubStore = GrubFs($storePath); } -my $extraInitrdPath; -if ($extraInitrd) { - if (! -f $extraInitrd) { - print STDERR "Warning: the specified extraInitrd " . $extraInitrd . " doesn't exist. Your system won't boot without it.\n"; - } - $extraInitrdPath = GrubFs($extraInitrd); -} # Generate the header. my $conf .= "# Automatically generated. DO NOT EDIT THIS FILE!\n"; @@ -249,12 +255,51 @@ if ($grubVersion == 1) { timeout $timeout "; if ($splashImage) { - copy $splashImage, "$bootPath/background.xpm.gz" or die "cannot copy $splashImage to $bootPath\n"; + copy $splashImage, "$bootPath/background.xpm.gz" or die "cannot copy $splashImage to $bootPath: $!\n"; $conf .= "splashimage " . ($grubBoot->path eq "/" ? "" : $grubBoot->path) . "/background.xpm.gz\n"; } } else { + 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; @@ -288,7 +333,7 @@ else { "; if ($font) { - copy $font, "$bootPath/converted-font.pf2" or die "cannot copy $font to $bootPath\n"; + 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 @@ -316,7 +361,7 @@ else { background_color '$backgroundColor' "; } - copy $splashImage, "$bootPath/background$suffix" or die "cannot copy $splashImage to $bootPath\n"; + 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 @@ -328,6 +373,28 @@ else { 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"; + $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"; @@ -350,22 +417,43 @@ sub copyToKernelsDir { # 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"; + 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) = @_; + my ($name, $path, $options) = @_; return unless -e "$path/kernel" && -e "$path/initrd"; my $kernel = copyToKernelsDir(Cwd::abs_path("$path/kernel")); my $initrd = copyToKernelsDir(Cwd::abs_path("$path/initrd")); - if ($extraInitrd) { - $initrd .= " " .$extraInitrdPath->path; + + # Include second initrd with secrets + if (-e -x "$path/append-initrd-secrets") { + my $initrdName = basename($initrd); + my $initrdSecretsPath = "$bootPath/kernels/$initrdName-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"); + system("$path/append-initrd-secrets", $initrdSecretsPathTemp) == 0 or die "failed to create initrd secrets: $!\n"; + # 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/$initrdName-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 @@ -383,14 +471,11 @@ sub addEntry { $conf .= " " . ($xen ? "module" : "kernel") . " $kernel $kernelParams\n"; $conf .= " " . ($xen ? "module" : "initrd") . " $initrd\n\n"; } else { - $conf .= "menuentry \"$name\" {\n"; + $conf .= "menuentry \"$name\" " . ($options||"") . " {\n"; $conf .= $grubBoot->search . "\n"; if ($copyKernels == 0) { $conf .= $grubStore->search . "\n"; } - if ($extraInitrd) { - $conf .= $extraInitrdPath->search . "\n"; - } $conf .= " $extraPerEntryConfig\n" if $extraPerEntryConfig; $conf .= " multiboot $xen $xenParams\n" if $xen; $conf .= " " . ($xen ? "module" : "linux") . " $kernel $kernelParams\n"; @@ -403,7 +488,7 @@ sub addEntry { # Add default entries. $conf .= "$extraEntries\n" if $extraEntriesBeforeNixOS; -addEntry("NixOS - Default", $defaultConfig); +addEntry("NixOS - Default", $defaultConfig, "--unrestricted"); $conf .= "$extraEntries\n" unless $extraEntriesBeforeNixOS; @@ -526,7 +611,7 @@ if (get("useOSProber") eq "true") { } # Atomically switch to the new config -rename $tmpFile, $confFile or die "cannot rename $tmpFile to $confFile\n"; +rename $tmpFile, $confFile or die "cannot rename $tmpFile to $confFile: $!\n"; # Remove obsolete files from $bootPath/kernels. @@ -547,9 +632,12 @@ struct(GrubState => { 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 => "" ); + my $defaultGrubState = GrubState->new(name => "", version => "", efi => "", devices => "", efiMountPoint => "", extraGrubInstallArgs => () ); open FILE, "<$bootPath/grub/state" or return $defaultGrubState; local $/ = "\n"; my $name = <FILE>; @@ -562,24 +650,37 @@ sub readGrubState { 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 ); + my $grubState = GrubState->new(name => $name, version => $version, efi => $efi, devices => $devices, efiMountPoint => $efiMountPoint, extraGrubInstallArgs => \@extraGrubInstallArgs ); return $grubState } -sub getDeviceTargets { - my @devices = (); - foreach my $dev ($dom->findnodes('/expr/attrs/attr[@name = "devices"]/list/string/@value')) { - $dev = $dev->findvalue(".") or die; - push(@devices, $dev); - } - return @devices; -} -my @deviceTargets = getDeviceTargets(); +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; @@ -588,25 +689,25 @@ 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 || $nameDiffer || $versionDiffer || $efiDiffer || $efiMountPointDiffer || (($ENV{'NIXOS_INSTALL_BOOTLOADER'} // "") eq "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"; +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 $grubVersion boot loader on $dev...\n"; - my @command = ("$grub/sbin/grub-install", "--recheck", "--root-directory=$tmpDir", Cwd::abs_path($dev)); + 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"; + (system @command) == 0 or die "$0: installation of GRUB on $dev failed: $!\n"; } } @@ -614,7 +715,7 @@ if (($requireNewInstall != 0) && ($efiTarget eq "no" || $efiTarget eq "both")) { # install EFI GRUB if (($requireNewInstall != 0) && ($efiTarget eq "only" || $efiTarget eq "both")) { print STDERR "installing the GRUB $grubVersion EFI boot loader into $efiSysMountPoint...\n"; - my @command = ("$grubEfi/sbin/grub-install", "--recheck", "--target=$grubTargetEfi", "--boot-directory=$bootPath", "--efi-directory=$efiSysMountPoint"); + my @command = ("$grubEfi/sbin/grub-install", "--recheck", "--target=$grubTargetEfi", "--boot-directory=$bootPath", "--efi-directory=$efiSysMountPoint", @extraGrubInstallArgs); if ($forceInstall eq "true") { push @command, "--force"; } @@ -625,17 +726,29 @@ if (($requireNewInstall != 0) && ($efiTarget eq "only" || $efiTarget eq "both")) push @command, "--removable" if $efiInstallAsRemovable eq "true"; } - (system @command) == 0 or die "$0: installation of GRUB EFI into $efiSysMountPoint failed\n"; + (system @command) == 0 or die "$0: installation of GRUB EFI into $efiSysMountPoint failed: $!\n"; } # update GRUB state file if ($requireNewInstall != 0) { - open FILE, ">$bootPath/grub/state" or die "cannot create $bootPath/grub/state: $!\n"; + # 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/init-script/init-script-builder.sh b/nixpkgs/nixos/modules/system/boot/loader/init-script/init-script-builder.sh index 6f48d2539ac..2a1ec479fea 100644 --- 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 @@ -53,7 +53,7 @@ addEntry() { echo "exec $stage2" )" - [ "$path" != "$defaultConfig" ] || { + [ "$path" != "$defaultConfig" ] || { echo "$content" > $tmp echo "# older configurations: $targetOther" >> $tmp chmod +x $tmp diff --git a/nixpkgs/nixos/modules/system/boot/loader/raspberrypi/raspberrypi-builder.nix b/nixpkgs/nixos/modules/system/boot/loader/raspberrypi/raspberrypi-builder.nix index e75aa9d1387..7eb52e3d021 100644 --- a/nixpkgs/nixos/modules/system/boot/loader/raspberrypi/raspberrypi-builder.nix +++ b/nixpkgs/nixos/modules/system/boot/loader/raspberrypi/raspberrypi-builder.nix @@ -3,8 +3,8 @@ pkgs.substituteAll { src = ./raspberrypi-builder.sh; isExecutable = true; - inherit (pkgs.buildPackages) bash; - path = with pkgs.buildPackages; [coreutils gnused gnugrep]; + inherit (pkgs) bash; + path = [pkgs.coreutils pkgs.gnused pkgs.gnugrep]; firmware = pkgs.raspberrypifw; inherit 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 index c8b5bf2e61a..0541ca1ba62 100644 --- a/nixpkgs/nixos/modules/system/boot/loader/raspberrypi/raspberrypi-builder.sh +++ b/nixpkgs/nixos/modules/system/boot/loader/raspberrypi/raspberrypi-builder.sh @@ -1,4 +1,7 @@ -#! @bash@/bin/sh -e +#! @bash@/bin/sh + +# This can end up being called disregarding the shebang. +set -e shopt -s nullglob 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 index f48a085ce57..65c7b825f85 100644 --- 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 @@ -47,9 +47,9 @@ def write_loader_conf(profile, generation): if "@timeout@" != "": f.write("timeout @timeout@\n") if profile: - f.write("default nixos-%s-generation-%d\n" % (profile, generation)) + f.write("default nixos-%s-generation-%d.conf\n" % (profile, generation)) else: - f.write("default nixos-generation-%d\n" % (generation)) + f.write("default nixos-generation-%d.conf\n" % (generation)) if not @editor@: f.write("editor 0\n"); f.write("console-mode @consoleMode@\n"); @@ -197,6 +197,24 @@ def main(): subprocess.check_call(["@systemd@/bin/bootctl", "--path=@efiSysMountPoint@", "install"]) else: subprocess.check_call(["@systemd@/bin/bootctl", "--path=@efiSysMountPoint@", "--no-variables", "install"]) + else: + # Update bootloader to latest if needed + systemd_version = subprocess.check_output(["@systemd@/bin/bootctl", "--version"], universal_newlines=True).split()[1] + # Ideally this should use check_output as well, but as a temporary + # work-around for #97433 we ignore any errors. + sdboot_status = subprocess.run(["@systemd@/bin/bootctl", "--path=@efiSysMountPoint@", "status"], universal_newlines=True, stdout=subprocess.PIPE).stdout + + # See status_binaries() in systemd bootctl.c for code which generates this + m = re.search("^\W+File:.*/EFI/(BOOT|systemd)/.*\.efi \(systemd-boot (\d+)\)$", + sdboot_status, re.IGNORECASE | re.MULTILINE) + if m is None: + print("could not find any previously installed systemd-boot") + else: + sdboot_version = m.group(2) + if systemd_version > sdboot_version: + print("updating systemd-boot from %s to %s" % (sdboot_version, systemd_version)) + subprocess.check_call(["@systemd@/bin/bootctl", "--path=@efiSysMountPoint@", "update"]) + mkdir_p("@efiSysMountPoint@/efi/nixos") mkdir_p("@efiSysMountPoint@/loader/entries") 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 index 22d459ceb04..f0bd76a3c1d 100644 --- a/nixpkgs/nixos/modules/system/boot/loader/systemd-boot/systemd-boot.nix +++ b/nixpkgs/nixos/modules/system/boot/loader/systemd-boot/systemd-boot.nix @@ -64,10 +64,10 @@ in { example = 120; type = types.nullOr types.int; description = '' - Maximum number of latest generations in the boot menu. + Maximum number of latest generations in the boot menu. Useful to prevent boot partition running out of disk space. - <literal>null</literal> means no limit i.e. all generations + <literal>null</literal> means no limit i.e. all generations that were not garbage collected yet. ''; }; diff --git a/nixpkgs/nixos/modules/system/boot/luksroot.nix b/nixpkgs/nixos/modules/system/boot/luksroot.nix index 31f1e22cda3..88190e8200b 100644 --- a/nixpkgs/nixos/modules/system/boot/luksroot.nix +++ b/nixpkgs/nixos/modules/system/boot/luksroot.nix @@ -140,7 +140,7 @@ let umount /crypt-ramfs 2>/dev/null ''; - openCommand = name': { name, device, header, keyFile, keyFileSize, keyFileOffset, allowDiscards, yubikey, gpgCard, fido2, fallbackToPassword, ... }: assert name' == name; + openCommand = name': { name, device, header, keyFile, keyFileSize, keyFileOffset, allowDiscards, yubikey, gpgCard, fido2, fallbackToPassword, preOpenCommands, postOpenCommands,... }: assert name' == name; let csopen = "cryptsetup luksOpen ${device} ${name} ${optionalString allowDiscards "--allow-discards"} ${optionalString (header != null) "--header=${header}"}"; cschange = "cryptsetup luksChangeKey ${device} ${optionalString (header != null) "--header=${header}"}"; @@ -412,11 +412,17 @@ let } ''} + # commands to run right before we mount our device + ${preOpenCommands} + ${if (luks.yubikeySupport && (yubikey != null)) || (luks.gpgSupport && (gpgCard != null)) || (luks.fido2Support && (fido2.credential != null)) then '' open_with_hardware '' else '' open_normally ''} + + # commands to run right after we mounted our device + ${postOpenCommands} ''; askPass = pkgs.writeScriptBin "cryptsetup-askpass" '' @@ -467,8 +473,6 @@ in [ "aes" "aes_generic" "blowfish" "twofish" "serpent" "cbc" "xts" "lrw" "sha1" "sha256" "sha512" "af_alg" "algif_skcipher" - - (if pkgs.stdenv.hostPlatform.system == "x86_64-linux" then "aes_x86_64" else "aes_i586") ]; description = '' A list of cryptographic kernel modules needed to decrypt the root device(s). @@ -512,7 +516,7 @@ in <filename>/dev/mapper/<replaceable>name</replaceable></filename>. ''; - type = with types; loaOf (submodule ( + type = with types; attrsOf (submodule ( { name, ... }: { options = { name = mkOption { @@ -637,7 +641,7 @@ in credential = mkOption { default = null; example = "f1d00200d8dc783f7fb1e10ace8da27f8312d72692abfca2f7e4960a73f48e82e1f7571f6ebfcee9fb434f9886ccc8fcc52a6614d8d2"; - type = types.str; + type = types.nullOr types.str; description = "The FIDO2 credential ID."; }; @@ -735,6 +739,30 @@ in }; }); }; + + preOpenCommands = mkOption { + type = types.lines; + default = ""; + example = '' + mkdir -p /tmp/persistent + mount -t zfs rpool/safe/persistent /tmp/persistent + ''; + description = '' + 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 partion. + ''; + }; + + postOpenCommands = mkOption { + type = types.lines; + default = ""; + example = '' + umount /tmp/persistent + ''; + description = '' + Commands that should be run right after we have mounted our LUKS device. + ''; + }; }; })); }; diff --git a/nixpkgs/nixos/modules/system/boot/modprobe.nix b/nixpkgs/nixos/modules/system/boot/modprobe.nix index dee0ab470c9..c75f32c4d99 100644 --- a/nixpkgs/nixos/modules/system/boot/modprobe.nix +++ b/nixpkgs/nixos/modules/system/boot/modprobe.nix @@ -28,7 +28,7 @@ with lib; Any additional configuration to be appended to the generated <filename>modprobe.conf</filename>. This is typically used to specify module options. See - <citerefentry><refentrytitle>modprobe.conf</refentrytitle> + <citerefentry><refentrytitle>modprobe.d</refentrytitle> <manvolnum>5</manvolnum></citerefentry> for details. ''; type = types.lines; diff --git a/nixpkgs/nixos/modules/system/boot/networkd.nix b/nixpkgs/nixos/modules/system/boot/networkd.nix index 9b34b12e73a..47689b2a470 100644 --- a/nixpkgs/nixos/modules/system/boot/networkd.nix +++ b/nixpkgs/nixos/modules/system/boot/networkd.nix @@ -8,359 +8,714 @@ let cfg = config.systemd.network; - checkLink = checkUnitConfig "Link" [ - (assertOnlyFields [ - "Description" "Alias" "MACAddressPolicy" "MACAddress" "NamePolicy" "Name" "OriginalName" - "MTUBytes" "BitsPerSecond" "Duplex" "AutoNegotiation" "WakeOnLan" "Port" "Advertise" - "TCPSegmentationOffload" "TCP6SegmentationOffload" "GenericSegmentationOffload" - "GenericReceiveOffload" "LargeReceiveOffload" "RxChannels" "TxChannels" - "OtherChannels" "CombinedChannels" - ]) - (assertValueOneOf "MACAddressPolicy" ["persistent" "random" "none"]) - (assertMacAddress "MACAddress") - (assertByteFormat "MTUBytes") - (assertByteFormat "BitsPerSecond") - (assertValueOneOf "Duplex" ["half" "full"]) - (assertValueOneOf "AutoNegotiation" boolValues) - (assertValueOneOf "WakeOnLan" ["phy" "unicast" "multicast" "broadcast" "arp" "magic" "secureon" "off"]) - (assertValueOneOf "Port" ["tp" "aui" "bnc" "mii" "fibre"]) - (assertValueOneOf "TCPSegmentationOffload" boolValues) - (assertValueOneOf "TCP6SegmentationOffload" boolValues) - (assertValueOneOf "GenericSegmentationOffload" boolValues) - (assertValueOneOf "UDPSegmentationOffload" boolValues) - (assertValueOneOf "GenericReceiveOffload" boolValues) - (assertValueOneOf "LargeReceiveOffload" boolValues) - (assertInt "RxChannels") - (assertMinimum "RxChannels" 1) - (assertInt "TxChannels") - (assertMinimum "TxChannels" 1) - (assertInt "OtherChannels") - (assertMinimum "OtherChannels" 1) - (assertInt "CombinedChannels") - (assertMinimum "CombinedChannels" 1) - ]; - - checkNetdev = checkUnitConfig "Netdev" [ - (assertOnlyFields [ - "Description" "Name" "Kind" "MTUBytes" "MACAddress" - ]) - (assertHasField "Name") - (assertHasField "Kind") - (assertValueOneOf "Kind" [ - "bond" "bridge" "dummy" "gre" "gretap" "ip6gre" "ip6tnl" "ip6gretap" "ipip" - "ipvlan" "macvlan" "macvtap" "sit" "tap" "tun" "veth" "vlan" "vti" "vti6" - "vxlan" "geneve" "vrf" "vcan" "vxcan" "wireguard" "netdevsim" "xfrm" - ]) - (assertByteFormat "MTUBytes") - (assertMacAddress "MACAddress") - ]; - - checkVRF = checkUnitConfig "VRF" [ - (assertOnlyFields [ "Table" ]) - (assertMinimum "Table" 0) - ]; + check = { + + 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" + ]) + (assertValueOneOf "MACAddressPolicy" ["persistent" "random" "none"]) + (assertMacAddress "MACAddress") + (assertByteFormat "MTUBytes") + (assertByteFormat "BitsPerSecond") + (assertValueOneOf "Duplex" ["half" "full"]) + (assertValueOneOf "AutoNegotiation" boolValues) + (assertValueOneOf "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") + ]; + }; - # 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. - checkWireGuard = checkUnitConfig "WireGuard" [ - (assertOnlyFields [ - "PrivateKeyFile" "ListenPort" "FwMark" - ]) - # The following check won't work on nix <= 2.2 - # see https://github.com/NixOS/nix/pull/2378 - # - # Add this again when we'll have drop the - # nix < 2.2 support. - # (assertRange "FwMark" 1 4294967295) - ]; + netdev = let + + tunChecks = [ + (assertOnlyFields [ + "MultiQueue" + "PacketInfo" + "VNetHeader" + "User" + "Group" + ]) + (assertValueOneOf "MultiQueue" boolValues) + (assertValueOneOf "PacketInfo" boolValues) + (assertValueOneOf "VNetHeader" boolValues) + ]; + 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" + "macvlan" + "macvtap" + "sit" + "tap" + "tun" + "veth" + "vlan" + "vti" + "vti6" + "vxlan" + "geneve" + "l2tp" + "macsec" + "vrf" + "vcan" + "vxcan" + "wireguard" + "netdevsim" + "nlmon" + "fou" + "xfrm" + "ifb" + ]) + (assertByteFormat "MTUBytes") + (assertMacAddress "MACAddress") + ]; - # 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. - checkWireGuardPeer = checkUnitConfig "WireGuardPeer" [ - (assertOnlyFields [ - "PublicKey" "PresharedKeyFile" "AllowedIPs" - "Endpoint" "PersistentKeepalive" - ]) - (assertRange "PersistentKeepalive" 1 65535) - ]; + 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) + ]; - checkVlan = checkUnitConfig "VLAN" [ - (assertOnlyFields ["Id" "GVRP" "MVRP" "LooseBinding" "ReorderHeader"]) - (assertRange "Id" 0 4094) - (assertValueOneOf "GVRP" boolValues) - (assertValueOneOf "MVRP" boolValues) - (assertValueOneOf "LooseBinding" boolValues) - (assertValueOneOf "ReorderHeader" boolValues) - ]; + sectionMACVLAN = checkUnitConfig "MACVLAN" [ + (assertOnlyFields [ + "Mode" + ]) + (assertValueOneOf "Mode" ["private" "vepa" "bridge" "passthru"]) + ]; - checkMacvlan = 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" + ]) + (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"])) + ]; - checkVxlan = checkUnitConfig "VXLAN" [ - (assertOnlyFields [ - "Id" "Remote" "Local" "TOS" "TTL" "MacLearning" "FDBAgeingSec" - "MaximumFDBEntries" "ReduceARPProxy" "L2MissNotification" - "L3MissNotification" "RouteShortCircuit" "UDPChecksum" - "UDP6ZeroChecksumTx" "UDP6ZeroChecksumRx" "RemoteChecksumTx" - "RemoteChecksumRx" "GroupPolicyExtension" "DestinationPort" "PortRange" - "FlowLabel" - ]) - (assertRange "TTL" 0 255) - (assertValueOneOf "MacLearning" boolValues) - (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) - (assertRange "FlowLabel" 0 1048575) - ]; + 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) + ]; - checkTunnel = checkUnitConfig "Tunnel" [ - (assertOnlyFields [ - "Local" "Remote" "TOS" "TTL" "DiscoverPathMTU" "IPv6FlowLabel" "CopyDSCP" - "EncapsulationLimit" "Key" "InputKey" "OutputKey" "Mode" "Independent" - "AllowLocalRemote" - ]) - (assertRange "TTL" 0 255) - (assertValueOneOf "DiscoverPathMTU" boolValues) - (assertValueOneOf "CopyDSCP" boolValues) - (assertValueOneOf "Mode" ["ip6ip6" "ipip6" "any"]) - (assertValueOneOf "Independent" boolValues) - (assertValueOneOf "AllowLocalRemote" boolValues) - ]; + sectionPeer = checkUnitConfig "Peer" [ + (assertOnlyFields [ + "Name" + "MACAddress" + ]) + (assertMacAddress "MACAddress") + ]; - checkPeer = checkUnitConfig "Peer" [ - (assertOnlyFields ["Name" "MACAddress"]) - (assertMacAddress "MACAddress") - ]; + sectionTun = checkUnitConfig "Tun" tunChecks; + + sectionTap = checkUnitConfig "Tap" tunChecks; + + # 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" + ]) + (assertInt "FirewallMark") + (assertRange "FirewallMark" 1 4294967295) + ]; - tunTapChecks = [ - (assertOnlyFields ["OneQueue" "MultiQueue" "PacketInfo" "VNetHeader" "User" "Group"]) - (assertValueOneOf "OneQueue" boolValues) - (assertValueOneOf "MultiQueue" boolValues) - (assertValueOneOf "PacketInfo" boolValues) - (assertValueOneOf "VNetHeader" boolValues) - ]; + # 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" + ]) + (assertInt "PersistentKeepalive") + (assertRange "PersistentKeepalive" 0 65535) + ]; - checkTun = checkUnitConfig "Tun" tunTapChecks; - - checkTap = checkUnitConfig "Tap" tunTapChecks; - - checkBond = checkUnitConfig "Bond" [ - (assertOnlyFields [ - "Mode" "TransmitHashPolicy" "LACPTransmitRate" "MIIMonitorSec" - "UpDelaySec" "DownDelaySec" "LearnPacketIntervalSec" "AdSelect" - "FailOverMACPolicy" "ARPValidate" "ARPIntervalSec" "ARPIPTargets" - "ARPAllTargets" "PrimaryReselectPolicy" "ResendIGMP" "PacketsPerSlave" - "GratuitousARP" "AllSlavesActive" "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"]) - (assertValueOneOf "FailOverMACPolicy" ["none" "active" "follow"]) - (assertValueOneOf "ARPValidate" ["none" "active" "backup" "all"]) - (assertValueOneOf "ARPAllTargets" ["any" "all"]) - (assertValueOneOf "PrimaryReselectPolicy" ["always" "better" "failure"]) - (assertRange "ResendIGMP" 0 255) - (assertRange "PacketsPerSlave" 0 65535) - (assertRange "GratuitousARP" 0 255) - (assertValueOneOf "AllSlavesActive" boolValues) - ]; + 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) + ]; - checkXfrm = checkUnitConfig "Xfrm" [ - (assertOnlyFields [ - "InterfaceId" "Independent" - ]) - # The following check won't work on nix <= 2.2 - # see https://github.com/NixOS/nix/pull/2378 - # - # Add this again when we'll have drop the - # nix < 2.2 support. - # (assertRange "InterfaceId" 1 4294967295) - (assertValueOneOf "Independent" boolValues) - ]; + sectionXfrm = checkUnitConfig "Xfrm" [ + (assertOnlyFields [ + "InterfaceId" + "Independent" + ]) + (assertInt "InterfaceId") + (assertRange "InterfaceId" 1 4294967295) + (assertValueOneOf "Independent" boolValues) + ]; - checkNetwork = checkUnitConfig "Network" [ - (assertOnlyFields [ - "Description" "DHCP" "DHCPServer" "LinkLocalAddressing" "IPv4LLRoute" - "IPv6Token" "LLMNR" "MulticastDNS" "DNSOverTLS" "DNSSEC" - "DNSSECNegativeTrustAnchors" "LLDP" "EmitLLDP" "BindCarrier" "Address" - "Gateway" "DNS" "Domains" "NTP" "IPForward" "IPMasquerade" - "IPv6PrivacyExtensions" "IPv6AcceptRA" "IPv6DuplicateAddressDetection" - "IPv6HopLimit" "IPv4ProxyARP" "IPv6ProxyNDP" "IPv6ProxyNDPAddress" - "IPv6PrefixDelegation" "IPv6MTUBytes" "Bridge" "Bond" "VRF" "VLAN" - "IPVLAN" "MACVLAN" "VXLAN" "Tunnel" "ActiveSlave" "PrimarySlave" - "ConfigureWithoutCarrier" "Xfrm" "KeepConfiguration" - ]) - # Note: For DHCP the values both, none, v4, v6 are deprecated - (assertValueOneOf "DHCP" ["yes" "no" "ipv4" "ipv6" "both" "none" "v4" "v6"]) - (assertValueOneOf "DHCPServer" boolValues) - (assertValueOneOf "LinkLocalAddressing" ["yes" "no" "ipv4" "ipv6" "ipv4-fallback" "fallback"]) - (assertValueOneOf "IPv4LLRoute" boolValues) - (assertValueOneOf "LLMNR" ["yes" "resolve" "no"]) - (assertValueOneOf "MulticastDNS" ["yes" "resolve" "no"]) - (assertValueOneOf "DNSOverTLS" ["opportunistic" "no"]) - (assertValueOneOf "DNSSEC" ["yes" "allow-downgrade" "no"]) - (assertValueOneOf "LLDP" ["yes" "routers-only" "no"]) - (assertValueOneOf "EmitLLDP" ["yes" "no" "nearest-bridge" "non-tpmr-bridge" "customer-bridge"]) - (assertValueOneOf "IPForward" ["yes" "no" "ipv4" "ipv6"]) - (assertValueOneOf "IPMasquerade" boolValues) - (assertValueOneOf "IPv6PrivacyExtensions" ["yes" "no" "prefer-public" "kernel"]) - (assertValueOneOf "IPv6AcceptRA" boolValues) - (assertValueOneOf "IPv4ProxyARP" boolValues) - (assertValueOneOf "IPv6ProxyNDP" boolValues) - (assertValueOneOf "IPv6PrefixDelegation" (boolValues ++ [ "dhcpv6" "static" ])) - (assertValueOneOf "ActiveSlave" boolValues) - (assertValueOneOf "PrimarySlave" boolValues) - (assertValueOneOf "ConfigureWithoutCarrier" boolValues) - (assertValueOneOf "KeepConfiguration" (boolValues ++ ["static" "dhcp-on-stop" "dhcp"])) - ]; + sectionVRF = checkUnitConfig "VRF" [ + (assertOnlyFields [ + "Table" + ]) + (assertInt "Table") + (assertMinimum "Table" 0) + ]; + }; - checkAddress = checkUnitConfig "Address" [ - (assertOnlyFields [ - "Address" "Peer" "Broadcast" "Label" "PreferredLifetime" "Scope" - "HomeAddress" "DuplicateAddressDetection" "ManageTemporaryAddress" - "PrefixRoute" "AutoJoin" - ]) - (assertHasField "Address") - (assertValueOneOf "PreferredLifetime" ["forever" "infinity" "0" 0]) - (assertValueOneOf "HomeAddress" boolValues) - (assertValueOneOf "DuplicateAddressDetection" boolValues) - (assertValueOneOf "ManageTemporaryAddress" boolValues) - (assertValueOneOf "PrefixRoute" boolValues) - (assertValueOneOf "AutoJoin" boolValues) - ]; + network = { + + sectionLink = checkUnitConfig "Link" [ + (assertOnlyFields [ + "MACAddress" + "MTUBytes" + "ARP" + "Multicast" + "AllMulticast" + "Unmanaged" + "RequiredForOnline" + ]) + (assertMacAddress "MACAddress") + (assertByteFormat "MTUBytes") + (assertValueOneOf "ARP" boolValues) + (assertValueOneOf "Multicast" boolValues) + (assertValueOneOf "AllMulticast" boolValues) + (assertValueOneOf "Unmanaged" boolValues) + (assertValueOneOf "RequiredForOnline" (boolValues ++ [ + "missing" + "off" + "no-carrier" + "dormant" + "degraded-carrier" + "carrier" + "degraded" + "enslaved" + "routable" + ])) + ]; - checkRoutingPolicyRule = checkUnitConfig "RoutingPolicyRule" [ - (assertOnlyFields [ - "TypeOfService" "From" "To" "FirewallMark" "Table" "Priority" - "IncomingInterface" "OutgoingInterface" "SourcePort" "DestinationPort" - "IPProtocol" "InvertRule" "Family" - ]) - (assertRange "TypeOfService" 0 255) - # The following check won't work on nix <= 2.2 - # see https://github.com/NixOS/nix/pull/2378 - # - # Add this again when we'll have drop the - # nix < 2.2 support. - # (assertRange "FirewallMark" 1 4294967295) - (assertInt "Priority") - (assertPort "SourcePort") - (assertPort "DestinationPort") - (assertValueOneOf "InvertRule" boolValues) - (assertValueOneOf "Family" ["ipv4" "ipv6" "both"]) - ]; + sectionNetwork = checkUnitConfig "Network" [ + (assertOnlyFields [ + "Description" + "DHCP" + "DHCPServer" + "LinkLocalAddressing" + "IPv4LLRoute" + "DefaultRouteOnDevice" + "IPv6Token" + "LLMNR" + "MulticastDNS" + "DNSOverTLS" + "DNSSEC" + "DNSSECNegativeTrustAnchors" + "LLDP" + "EmitLLDP" + "BindCarrier" + "Address" + "Gateway" + "DNS" + "Domains" + "DNSDefaultRoute" + "NTP" + "IPForward" + "IPMasquerade" + "IPv6PrivacyExtensions" + "IPv6AcceptRA" + "IPv6DuplicateAddressDetection" + "IPv6HopLimit" + "IPv4ProxyARP" + "IPv6ProxyNDP" + "IPv6ProxyNDPAddress" + "IPv6PrefixDelegation" + "IPv6MTUBytes" + "Bridge" + "Bond" + "VRF" + "VLAN" + "IPVLAN" + "MACVLAN" + "VXLAN" + "Tunnel" + "MACsec" + "ActiveSlave" + "PrimarySlave" + "ConfigureWithoutCarrier" + "IgnoreCarrierLoss" + "Xfrm" + "KeepConfiguration" + ]) + # 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 "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) + (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 "IPv6PrefixDelegation" ["static" "dhcpv6" "yes" "false"]) + (assertByteFormat "IPv6MTUBytes") + (assertValueOneOf "ActiveSlave" boolValues) + (assertValueOneOf "PrimarySlave" boolValues) + (assertValueOneOf "ConfigureWithoutCarrier" boolValues) + (assertValueOneOf "IgnoreCarrierLoss" boolValues) + (assertValueOneOf "KeepConfiguration" (boolValues ++ ["static" "dhcp-on-stop" "dhcp"])) + ]; - checkRoute = checkUnitConfig "Route" [ - (assertOnlyFields [ - "Gateway" "GatewayOnLink" "Destination" "Source" "Metric" - "IPv6Preference" "Scope" "PreferredSource" "Table" "Protocol" "Type" - "InitialCongestionWindow" "InitialAdvertisedReceiveWindow" "QuickAck" - "MTUBytes" - ]) - ]; + sectionAddress = checkUnitConfig "Address" [ + (assertOnlyFields [ + "Address" + "Peer" + "Broadcast" + "Label" + "PreferredLifetime" + "Scope" + "HomeAddress" + "DuplicateAddressDetection" + "ManageTemporaryAddress" + "AddPrefixRoute" + "AutoJoin" + ]) + (assertHasField "Address") + (assertValueOneOf "PreferredLifetime" ["forever" "infinity" "0" 0]) + (assertValueOneOf "HomeAddress" boolValues) + (assertValueOneOf "DuplicateAddressDetection" ["ipv4" "ipv6" "both" "none"]) + (assertValueOneOf "ManageTemporaryAddress" boolValues) + (assertValueOneOf "AddPrefixRoute" boolValues) + (assertValueOneOf "AutoJoin" boolValues) + ]; - checkDhcpV4 = checkUnitConfig "DHCPv4" [ - (assertOnlyFields [ - "UseDNS" "RoutesToDNS" "UseNTP" "UseMTU" "Anonymize" "SendHostname" "UseHostname" - "Hostname" "UseDomains" "UseRoutes" "UseTimezone" - "ClientIdentifier" "VendorClassIdentifier" "UserClass" "MaxAttempts" - "DUIDType" "DUIDRawData" "IAID" "RequestBroadcast" "RouteMetric" "RouteTable" - "ListenPort" "SendRelease" - ]) - (assertValueOneOf "UseDNS" boolValues) - (assertValueOneOf "RoutesToDNS" boolValues) - (assertValueOneOf "UseNTP" boolValues) - (assertValueOneOf "UseMTU" boolValues) - (assertValueOneOf "Anonymize" boolValues) - (assertValueOneOf "SendHostname" boolValues) - (assertValueOneOf "UseHostname" boolValues) - (assertValueOneOf "UseDomains" ["yes" "no" "route"]) - (assertValueOneOf "UseRoutes" boolValues) - (assertValueOneOf "UseTimezone" boolValues) - (assertMinimum "MaxAttempts" 0) - (assertValueOneOf "RequestBroadcast" boolValues) - (assertInt "RouteTable") - (assertMinimum "RouteTable" 0) - (assertValueOneOf "SendRelease" boolValues) - ]; + sectionRoutingPolicyRule = checkUnitConfig "RoutingPolicyRule" [ + (assertOnlyFields [ + "TypeOfService" + "From" + "To" + "FirewallMark" + "Table" + "Priority" + "IncomingInterface" + "OutgoingInterface" + "SourcePort" + "DestinationPort" + "IPProtocol" + "InvertRule" + "Family" + "User" + "SuppressPrefixLength" + ]) + (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) + ]; - checkDhcpV6 = checkUnitConfig "DHCPv6" [ - (assertOnlyFields [ - "UseDns" "UseNTP" "RapidCommit" "ForceDHCPv6PDOtherInformation" - "PrefixDelegationHint" - ]) - (assertValueOneOf "UseDNS" boolValues) - (assertValueOneOf "UseNTP" boolValues) - (assertValueOneOf "RapidCommit" boolValues) - (assertValueOneOf "ForceDHCPv6PDOtherInformation" boolValues) - ]; + 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"]) + ]; - checkIpv6PrefixDelegation = checkUnitConfig "IPv6PrefixDelegation" [ - (assertOnlyFields [ - "Managed" "OtherInformation" "RouterLifetimeSec" - "RouterPreference" "EmitDNS" "DNS" "EmitDomains" "Domains" - "DNSLifetimeSec" - ]) - (assertValueOneOf "Managed" boolValues) - (assertValueOneOf "OtherInformation" boolValues) - (assertValueOneOf "RouterPreference" ["high" "medium" "low" "normal" "default"]) - (assertValueOneOf "EmitDNS" boolValues) - (assertValueOneOf "EmitDomains" boolValues) - (assertMinimum "DNSLifetimeSec" 0) - ]; + 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" + ]) + (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) + ]; - checkIpv6Prefix = checkUnitConfig "IPv6Prefix" [ - (assertOnlyFields [ - "AddressAutoconfiguration" "OnLink" "Prefix" - "PreferredLifetimeSec" "ValidLifetimeSec" - ]) - (assertValueOneOf "AddressAutoconfiguration" boolValues) - (assertValueOneOf "OnLink" boolValues) - (assertMinimum "PreferredLifetimeSec" 0) - (assertMinimum "ValidLifetimeSec" 0) - ]; + sectionDHCPv6 = checkUnitConfig "DHCPv6" [ + (assertOnlyFields [ + "UseDNS" + "UseNTP" + "RapidCommit" + "ForceDHCPv6PDOtherInformation" + "PrefixDelegationHint" + ]) + (assertValueOneOf "UseDNS" boolValues) + (assertValueOneOf "UseNTP" boolValues) + (assertValueOneOf "RapidCommit" boolValues) + (assertValueOneOf "ForceDHCPv6PDOtherInformation" boolValues) + ]; + sectionDHCPServer = checkUnitConfig "DHCPServer" [ + (assertOnlyFields [ + "PoolOffset" + "PoolSize" + "DefaultLeaseTimeSec" + "MaxLeaseTimeSec" + "EmitDNS" + "DNS" + "EmitNTP" + "NTP" + "EmitSIP" + "SIP" + "EmitRouter" + "EmitTimezone" + "Timezone" + "SendOption" + ]) + (assertInt "PoolOffset") + (assertMinimum "PoolOffset" 0) + (assertInt "PoolSize") + (assertMinimum "PoolSize" 0) + (assertValueOneOf "EmitDNS" boolValues) + (assertValueOneOf "EmitNTP" boolValues) + (assertValueOneOf "EmitSIP" boolValues) + (assertValueOneOf "EmitRouter" boolValues) + (assertValueOneOf "EmitTimezone" boolValues) + ]; - checkDhcpServer = checkUnitConfig "DHCPServer" [ - (assertOnlyFields [ - "PoolOffset" "PoolSize" "DefaultLeaseTimeSec" "MaxLeaseTimeSec" - "EmitDNS" "DNS" "EmitNTP" "NTP" "EmitRouter" "EmitTimezone" "Timezone" - ]) - (assertValueOneOf "EmitDNS" boolValues) - (assertValueOneOf "EmitNTP" boolValues) - (assertValueOneOf "EmitRouter" boolValues) - (assertValueOneOf "EmitTimezone" boolValues) - ]; + sectionIPv6PrefixDelegation = checkUnitConfig "IPv6PrefixDelegation" [ + (assertOnlyFields [ + "Managed" + "OtherInformation" + "RouterLifetimeSec" + "RouterPreference" + "EmitDNS" + "DNS" + "EmitDomains" + "Domains" + "DNSLifetimeSec" + ]) + (assertValueOneOf "Managed" boolValues) + (assertValueOneOf "OtherInformation" boolValues) + (assertValueOneOf "RouterPreference" ["high" "medium" "low" "normal" "default"]) + (assertValueOneOf "EmitDNS" boolValues) + (assertValueOneOf "EmitDomains" boolValues) + ]; - # .network files have a [Link] section with different options than in .netlink files - checkNetworkLink = checkUnitConfig "Link" [ - (assertOnlyFields [ - "MACAddress" "MTUBytes" "ARP" "Multicast" "Unmanaged" "RequiredForOnline" - ]) - (assertMacAddress "MACAddress") - (assertByteFormat "MTUBytes") - (assertValueOneOf "ARP" boolValues) - (assertValueOneOf "Multicast" boolValues) - (assertValueOneOf "Unmanaged" boolValues) - (assertValueOneOf "RequiredForOnline" (boolValues ++ ["off" "no-carrier" "dormant" "degraded-carrier" "carrier" "degraded" "enslaved" "routable"])) - ]; + sectionIPv6Prefix = checkUnitConfig "IPv6Prefix" [ + (assertOnlyFields [ + "AddressAutoconfiguration" + "OnLink" + "Prefix" + "PreferredLifetimeSec" + "ValidLifetimeSec" + ]) + (assertValueOneOf "AddressAutoconfiguration" boolValues) + (assertValueOneOf "OnLink" boolValues) + ]; + }; + }; commonNetworkOptions = { @@ -406,7 +761,7 @@ let linkConfig = mkOption { default = {}; example = { MACAddress = "00:ff:ee:aa:cc:dd"; }; - type = types.addCheck (types.attrsOf unitOption) checkLink; + type = types.addCheck (types.attrsOf unitOption) check.link.sectionLink; description = '' Each attribute in this set specifies an option in the <literal>[Link]</literal> section of the unit. See @@ -417,12 +772,28 @@ let }; + wireguardPeerOptions = { + options = { + wireguardPeerConfig = mkOption { + default = {}; + example = { }; + type = types.addCheck (types.attrsOf unitOption) check.netdev.sectionWireGuardPeer; + description = '' + Each attribute in this set specifies an option in the + <literal>[WireGuardPeer]</literal> section of the unit. See + <citerefentry><refentrytitle>systemd.network</refentrytitle> + <manvolnum>5</manvolnum></citerefentry> for details. + ''; + }; + }; + }; + netdevOptions = commonNetworkOptions // { netdevConfig = mkOption { default = {}; example = { Name = "mybridge"; Kind = "bridge"; }; - type = types.addCheck (types.attrsOf unitOption) checkNetdev; + type = types.addCheck (types.attrsOf unitOption) check.netdev.sectionNetdev; description = '' Each attribute in this set specifies an option in the <literal>[Netdev]</literal> section of the unit. See @@ -431,65 +802,10 @@ let ''; }; - vrfConfig = mkOption { - default = {}; - example = { Table = 2342; }; - type = types.addCheck (types.attrsOf unitOption) checkVRF; - description = '' - Each attribute in this set specifies an option in the - <literal>[VRF]</literal> section of the unit. See - <citerefentry><refentrytitle>systemd.netdev</refentrytitle> - <manvolnum>5</manvolnum></citerefentry> for details. - A detailed explanation about how VRFs work can be found in the - <link xlink:href="https://www.kernel.org/doc/Documentation/networking/vrf.txt">kernel - docs</link>. - ''; - }; - - wireguardConfig = mkOption { - default = {}; - example = { - PrivateKeyFile = "/etc/wireguard/secret.key"; - ListenPort = 51820; - FwMark = 42; - }; - type = types.addCheck (types.attrsOf unitOption) checkWireGuard; - description = '' - Each attribute in this set specifies an option in the - <literal>[WireGuard]</literal> section of the unit. See - <citerefentry><refentrytitle>systemd.netdev</refentrytitle> - <manvolnum>5</manvolnum></citerefentry> for details. - Use <literal>PrivateKeyFile</literal> instead of - <literal>PrivateKey</literal>: 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 = '' - Each item in this array specifies an option in the - <literal>[WireGuardPeer]</literal> section of the unit. See - <citerefentry><refentrytitle>systemd.netdev</refentrytitle> - <manvolnum>5</manvolnum></citerefentry> for details. - Use <literal>PresharedKeyFile</literal> instead of - <literal>PresharedKey</literal>: the nix store is - world-readable. - ''; - }; - vlanConfig = mkOption { default = {}; - example = { Id = "4"; }; - type = types.addCheck (types.attrsOf unitOption) checkVlan; + example = { Id = 4; }; + type = types.addCheck (types.attrsOf unitOption) check.netdev.sectionVLAN; description = '' Each attribute in this set specifies an option in the <literal>[VLAN]</literal> section of the unit. See @@ -501,7 +817,7 @@ let macvlanConfig = mkOption { default = {}; example = { Mode = "private"; }; - type = types.addCheck (types.attrsOf unitOption) checkMacvlan; + type = types.addCheck (types.attrsOf unitOption) check.netdev.sectionMACVLAN; description = '' Each attribute in this set specifies an option in the <literal>[MACVLAN]</literal> section of the unit. See @@ -513,7 +829,7 @@ let vxlanConfig = mkOption { default = {}; example = { Id = "4"; }; - type = types.addCheck (types.attrsOf unitOption) checkVxlan; + type = types.addCheck (types.attrsOf unitOption) check.netdev.sectionVXLAN; description = '' Each attribute in this set specifies an option in the <literal>[VXLAN]</literal> section of the unit. See @@ -525,7 +841,7 @@ let tunnelConfig = mkOption { default = {}; example = { Remote = "192.168.1.1"; }; - type = types.addCheck (types.attrsOf unitOption) checkTunnel; + type = types.addCheck (types.attrsOf unitOption) check.netdev.sectionTunnel; description = '' Each attribute in this set specifies an option in the <literal>[Tunnel]</literal> section of the unit. See @@ -537,7 +853,7 @@ let peerConfig = mkOption { default = {}; example = { Name = "veth2"; }; - type = types.addCheck (types.attrsOf unitOption) checkPeer; + type = types.addCheck (types.attrsOf unitOption) check.netdev.sectionPeer; description = '' Each attribute in this set specifies an option in the <literal>[Peer]</literal> section of the unit. See @@ -549,7 +865,7 @@ let tunConfig = mkOption { default = {}; example = { User = "openvpn"; }; - type = types.addCheck (types.attrsOf unitOption) checkTun; + type = types.addCheck (types.attrsOf unitOption) check.netdev.sectionTun; description = '' Each attribute in this set specifies an option in the <literal>[Tun]</literal> section of the unit. See @@ -561,7 +877,7 @@ let tapConfig = mkOption { default = {}; example = { User = "openvpn"; }; - type = types.addCheck (types.attrsOf unitOption) checkTap; + type = types.addCheck (types.attrsOf unitOption) check.netdev.sectionTap; description = '' Each attribute in this set specifies an option in the <literal>[Tap]</literal> section of the unit. See @@ -570,10 +886,50 @@ let ''; }; + wireguardConfig = mkOption { + default = {}; + example = { + PrivateKeyFile = "/etc/wireguard/secret.key"; + ListenPort = 51820; + FwMark = 42; + }; + type = types.addCheck (types.attrsOf unitOption) check.netdev.sectionWireGuard; + description = '' + Each attribute in this set specifies an option in the + <literal>[WireGuard]</literal> section of the unit. See + <citerefentry><refentrytitle>systemd.netdev</refentrytitle> + <manvolnum>5</manvolnum></citerefentry> for details. + Use <literal>PrivateKeyFile</literal> instead of + <literal>PrivateKey</literal>: 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 = '' + Each item in this array specifies an option in the + <literal>[WireGuardPeer]</literal> section of the unit. See + <citerefentry><refentrytitle>systemd.netdev</refentrytitle> + <manvolnum>5</manvolnum></citerefentry> for details. + Use <literal>PresharedKeyFile</literal> instead of + <literal>PresharedKey</literal>: the nix store is + world-readable. + ''; + }; + bondConfig = mkOption { default = {}; example = { Mode = "802.3ad"; }; - type = types.addCheck (types.attrsOf unitOption) checkBond; + type = types.addCheck (types.attrsOf unitOption) check.netdev.sectionBond; description = '' Each attribute in this set specifies an option in the <literal>[Bond]</literal> section of the unit. See @@ -585,7 +941,7 @@ let xfrmConfig = mkOption { default = {}; example = { InterfaceId = 1; }; - type = types.addCheck (types.attrsOf unitOption) checkXfrm; + type = types.addCheck (types.attrsOf unitOption) check.netdev.sectionXfrm; description = '' Each attribute in this set specifies an option in the <literal>[Xfrm]</literal> section of the unit. See @@ -594,6 +950,21 @@ let ''; }; + vrfConfig = mkOption { + default = {}; + example = { Table = 2342; }; + type = types.addCheck (types.attrsOf unitOption) check.netdev.sectionVRF; + description = '' + Each attribute in this set specifies an option in the + <literal>[VRF]</literal> section of the unit. See + <citerefentry><refentrytitle>systemd.netdev</refentrytitle> + <manvolnum>5</manvolnum></citerefentry> for details. + A detailed explanation about how VRFs work can be found in the + <link xlink:href="https://www.kernel.org/doc/Documentation/networking/vrf.txt">kernel + docs</link>. + ''; + }; + }; addressOptions = { @@ -601,7 +972,7 @@ let addressConfig = mkOption { default = {}; example = { Address = "192.168.0.100/24"; }; - type = types.addCheck (types.attrsOf unitOption) checkAddress; + type = types.addCheck (types.attrsOf unitOption) check.network.sectionAddress; description = '' Each attribute in this set specifies an option in the <literal>[Address]</literal> section of the unit. See @@ -617,7 +988,7 @@ let routingPolicyRuleConfig = mkOption { default = { }; example = { routingPolicyRuleConfig = { Table = 10; IncomingInterface = "eth1"; Family = "both"; } ;}; - type = types.addCheck (types.attrsOf unitOption) checkRoutingPolicyRule; + type = types.addCheck (types.attrsOf unitOption) check.network.sectionRoutingPolicyRule; description = '' Each attribute in this set specifies an option in the <literal>[RoutingPolicyRule]</literal> section of the unit. See @@ -633,7 +1004,7 @@ let routeConfig = mkOption { default = {}; example = { Gateway = "192.168.0.1"; }; - type = types.addCheck (types.attrsOf unitOption) checkRoute; + type = types.addCheck (types.attrsOf unitOption) check.network.sectionRoute; description = '' Each attribute in this set specifies an option in the <literal>[Route]</literal> section of the unit. See @@ -644,28 +1015,12 @@ let }; }; - wireguardPeerOptions = { - options = { - wireguardPeerConfig = mkOption { - default = {}; - example = { }; - type = types.addCheck (types.attrsOf unitOption) checkWireGuardPeer; - description = '' - Each attribute in this set specifies an option in the - <literal>[WireGuardPeer]</literal> section of the unit. See - <citerefentry><refentrytitle>systemd.network</refentrytitle> - <manvolnum>5</manvolnum></citerefentry> for details. - ''; - }; - }; - }; - ipv6PrefixOptions = { options = { ipv6PrefixConfig = mkOption { default = {}; example = { Prefix = "fd00::/64"; }; - type = types.addCheck (types.attrsOf unitOption) checkIpv6Prefix; + type = types.addCheck (types.attrsOf unitOption) check.network.sectionIPv6Prefix; description = '' Each attribute in this set specifies an option in the <literal>[IPv6Prefix]</literal> section of the unit. See @@ -676,13 +1031,24 @@ let }; }; - networkOptions = commonNetworkOptions // { + linkConfig = mkOption { + default = {}; + example = { Unmanaged = true; }; + type = types.addCheck (types.attrsOf unitOption) check.network.sectionLink; + description = '' + Each attribute in this set specifies an option in the + <literal>[Link]</literal> section of the unit. See + <citerefentry><refentrytitle>systemd.network</refentrytitle> + <manvolnum>5</manvolnum></citerefentry> for details. + ''; + }; + networkConfig = mkOption { default = {}; example = { Description = "My Network"; }; - type = types.addCheck (types.attrsOf unitOption) checkNetwork; + type = types.addCheck (types.attrsOf unitOption) check.network.sectionNetwork; description = '' Each attribute in this set specifies an option in the <literal>[Network]</literal> section of the unit. See @@ -701,7 +1067,7 @@ let dhcpV4Config = mkOption { default = {}; example = { UseDNS = true; UseRoutes = true; }; - type = types.addCheck (types.attrsOf unitOption) checkDhcpV4; + type = types.addCheck (types.attrsOf unitOption) check.network.sectionDHCPv4; description = '' Each attribute in this set specifies an option in the <literal>[DHCPv4]</literal> section of the unit. See @@ -713,7 +1079,7 @@ let dhcpV6Config = mkOption { default = {}; example = { UseDNS = true; UseRoutes = true; }; - type = types.addCheck (types.attrsOf unitOption) checkDhcpV6; + type = types.addCheck (types.attrsOf unitOption) check.network.sectionDHCPv6; description = '' Each attribute in this set specifies an option in the <literal>[DHCPv6]</literal> section of the unit. See @@ -722,48 +1088,36 @@ let ''; }; - ipv6PrefixDelegationConfig = mkOption { + dhcpServerConfig = mkOption { default = {}; - example = { EmitDNS = true; Managed = true; OtherInformation = true; }; - type = types.addCheck (types.attrsOf unitOption) checkIpv6PrefixDelegation; + example = { PoolOffset = 50; EmitDNS = false; }; + type = types.addCheck (types.attrsOf unitOption) check.network.sectionDHCPServer; description = '' Each attribute in this set specifies an option in the - <literal>[IPv6PrefixDelegation]</literal> section of the unit. See - <citerefentry><refentrytitle>systemd.network</refentrytitle> - <manvolnum>5</manvolnum></citerefentry> for details. - ''; - }; - - ipv6Prefixes = mkOption { - default = []; - example = { AddressAutoconfiguration = true; OnLink = true; }; - type = with types; listOf (submodule ipv6PrefixOptions); - description = '' - A list of ipv6Prefix sections to be added to the unit. See + <literal>[DHCPServer]</literal> section of the unit. See <citerefentry><refentrytitle>systemd.network</refentrytitle> <manvolnum>5</manvolnum></citerefentry> for details. ''; }; - dhcpServerConfig = mkOption { + ipv6PrefixDelegationConfig = mkOption { default = {}; - example = { PoolOffset = 50; EmitDNS = false; }; - type = types.addCheck (types.attrsOf unitOption) checkDhcpServer; + example = { EmitDNS = true; Managed = true; OtherInformation = true; }; + type = types.addCheck (types.attrsOf unitOption) check.network.sectionIPv6PrefixDelegation; description = '' Each attribute in this set specifies an option in the - <literal>[DHCPServer]</literal> section of the unit. See + <literal>[IPv6PrefixDelegation]</literal> section of the unit. See <citerefentry><refentrytitle>systemd.network</refentrytitle> <manvolnum>5</manvolnum></citerefentry> for details. ''; }; - linkConfig = mkOption { - default = {}; - example = { Unmanaged = true; }; - type = types.addCheck (types.attrsOf unitOption) checkNetworkLink; + ipv6Prefixes = mkOption { + default = []; + example = { AddressAutoconfiguration = true; OnLink = true; }; + type = with types; listOf (submodule ipv6PrefixOptions); description = '' - Each attribute in this set specifies an option in the - <literal>[Link]</literal> section of the unit. See + A list of ipv6Prefix sections to be added to the unit. See <citerefentry><refentrytitle>systemd.network</refentrytitle> <manvolnum>5</manvolnum></citerefentry> for details. ''; @@ -958,160 +1312,162 @@ let }; }; - commonMatchText = def: optionalString (def.matchConfig != {}) '' + commonMatchText = def: optionalString (def.matchConfig != { }) '' [Match] ${attrsToSection def.matchConfig} ''; linkToUnit = name: def: { inherit (def) enable; - text = commonMatchText def + - '' + text = commonMatchText def + + '' [Link] ${attrsToSection def.linkConfig} - - ${def.extraConfig} - ''; + '' + + def.extraConfig; }; netdevToUnit = name: def: { inherit (def) enable; - text = commonMatchText def + - '' + text = commonMatchText def + + '' [NetDev] ${attrsToSection def.netdevConfig} - - ${optionalString (def.vlanConfig != { }) '' - [VLAN] - ${attrsToSection def.vlanConfig} - - ''} - ${optionalString (def.macvlanConfig != { }) '' - [MACVLAN] - ${attrsToSection def.macvlanConfig} - - ''} - ${optionalString (def.vxlanConfig != { }) '' - [VXLAN] - ${attrsToSection def.vxlanConfig} - - ''} - ${optionalString (def.tunnelConfig != { }) '' - [Tunnel] - ${attrsToSection def.tunnelConfig} - - ''} - ${optionalString (def.peerConfig != { }) '' - [Peer] - ${attrsToSection def.peerConfig} - - ''} - ${optionalString (def.tunConfig != { }) '' - [Tun] - ${attrsToSection def.tunConfig} - - ''} - ${optionalString (def.tapConfig != { }) '' - [Tap] - ${attrsToSection def.tapConfig} - - ''} - ${optionalString (def.bondConfig != { }) '' - [Bond] - ${attrsToSection def.bondConfig} - - ''} - ${optionalString (def.xfrmConfig != { }) '' - [Xfrm] - ${attrsToSection def.xfrmConfig} - - ''} - ${optionalString (def.vrfConfig != { }) '' - [VRF] - ${attrsToSection def.vrfConfig} - - ''} - ${optionalString (def.wireguardConfig != { }) '' - [WireGuard] - ${attrsToSection def.wireguardConfig} - - ''} - ${flip concatMapStrings def.wireguardPeers (x: '' - [WireGuardPeer] - ${attrsToSection x.wireguardPeerConfig} - - '')} - ${def.extraConfig} - ''; + '' + + optionalString (def.vlanConfig != { }) '' + [VLAN] + ${attrsToSection def.vlanConfig} + '' + + optionalString (def.macvlanConfig != { }) '' + [MACVLAN] + ${attrsToSection def.macvlanConfig} + '' + + optionalString (def.vxlanConfig != { }) '' + [VXLAN] + ${attrsToSection def.vxlanConfig} + '' + + optionalString (def.tunnelConfig != { }) '' + [Tunnel] + ${attrsToSection def.tunnelConfig} + '' + + optionalString (def.peerConfig != { }) '' + [Peer] + ${attrsToSection def.peerConfig} + '' + + optionalString (def.tunConfig != { }) '' + [Tun] + ${attrsToSection def.tunConfig} + '' + + optionalString (def.tapConfig != { }) '' + [Tap] + ${attrsToSection def.tapConfig} + '' + + optionalString (def.wireguardConfig != { }) '' + [WireGuard] + ${attrsToSection def.wireguardConfig} + '' + + flip concatMapStrings def.wireguardPeers (x: '' + [WireGuardPeer] + ${attrsToSection x.wireguardPeerConfig} + '') + + optionalString (def.bondConfig != { }) '' + [Bond] + ${attrsToSection def.bondConfig} + '' + + optionalString (def.xfrmConfig != { }) '' + [Xfrm] + ${attrsToSection def.xfrmConfig} + '' + + optionalString (def.vrfConfig != { }) '' + [VRF] + ${attrsToSection def.vrfConfig} + '' + + def.extraConfig; }; networkToUnit = name: def: { inherit (def) enable; - text = commonMatchText def + + text = commonMatchText def + + optionalString (def.linkConfig != { }) '' + [Link] + ${attrsToSection def.linkConfig} '' - ${optionalString (def.linkConfig != { }) '' - [Link] - ${attrsToSection def.linkConfig} - - ''} - + + '' [Network] - ${attrsToSection def.networkConfig} + '' + + attrsToSection def.networkConfig + + optionalString (def.address != [ ]) '' ${concatStringsSep "\n" (map (s: "Address=${s}") def.address)} + '' + + optionalString (def.gateway != [ ]) '' ${concatStringsSep "\n" (map (s: "Gateway=${s}") def.gateway)} + '' + + optionalString (def.dns != [ ]) '' ${concatStringsSep "\n" (map (s: "DNS=${s}") def.dns)} + '' + + optionalString (def.ntp != [ ]) '' ${concatStringsSep "\n" (map (s: "NTP=${s}") def.ntp)} + '' + + optionalString (def.bridge != [ ]) '' ${concatStringsSep "\n" (map (s: "Bridge=${s}") def.bridge)} + '' + + optionalString (def.bond != [ ]) '' ${concatStringsSep "\n" (map (s: "Bond=${s}") def.bond)} + '' + + optionalString (def.vrf != [ ]) '' ${concatStringsSep "\n" (map (s: "VRF=${s}") def.vrf)} + '' + + optionalString (def.vlan != [ ]) '' ${concatStringsSep "\n" (map (s: "VLAN=${s}") def.vlan)} + '' + + optionalString (def.macvlan != [ ]) '' ${concatStringsSep "\n" (map (s: "MACVLAN=${s}") def.macvlan)} + '' + + optionalString (def.vxlan != [ ]) '' ${concatStringsSep "\n" (map (s: "VXLAN=${s}") def.vxlan)} + '' + + optionalString (def.tunnel != [ ]) '' ${concatStringsSep "\n" (map (s: "Tunnel=${s}") def.tunnel)} + '' + + optionalString (def.xfrm != [ ]) '' ${concatStringsSep "\n" (map (s: "Xfrm=${s}") def.xfrm)} + '' + + '' - ${optionalString (def.dhcpV4Config != { }) '' - [DHCPv4] - ${attrsToSection def.dhcpV4Config} - - ''} - ${optionalString (def.dhcpV6Config != {}) '' - [DHCPv6] - ${attrsToSection def.dhcpV6Config} - - ''} - ${optionalString (def.ipv6PrefixDelegationConfig != {}) '' - [IPv6PrefixDelegation] - ${attrsToSection def.ipv6PrefixDelegationConfig} - - ''} - ${flip concatMapStrings def.ipv6Prefixes (x: '' - [IPv6Prefix] - ${attrsToSection x.ipv6PrefixConfig} - - '')} - ${optionalString (def.dhcpServerConfig != { }) '' - [DHCPServer] - ${attrsToSection def.dhcpServerConfig} - - ''} - ${flip concatMapStrings def.addresses (x: '' - [Address] - ${attrsToSection x.addressConfig} - - '')} - ${flip concatMapStrings def.routes (x: '' - [Route] - ${attrsToSection x.routeConfig} - - '')} - ${flip concatMapStrings def.routingPolicyRules (x: '' - [RoutingPolicyRule] - ${attrsToSection x.routingPolicyRuleConfig} - - '')} - ${def.extraConfig} - ''; + '' + + flip concatMapStrings def.addresses (x: '' + [Address] + ${attrsToSection x.addressConfig} + '') + + flip concatMapStrings def.routingPolicyRules (x: '' + [RoutingPolicyRule] + ${attrsToSection x.routingPolicyRuleConfig} + '') + + flip concatMapStrings def.routes (x: '' + [Route] + ${attrsToSection x.routeConfig} + '') + + optionalString (def.dhcpV4Config != { }) '' + [DHCPv4] + ${attrsToSection def.dhcpV4Config} + '' + + optionalString (def.dhcpV6Config != { }) '' + [DHCPv6] + ${attrsToSection def.dhcpV6Config} + '' + + optionalString (def.dhcpServerConfig != { }) '' + [DHCPServer] + ${attrsToSection def.dhcpServerConfig} + '' + + optionalString (def.ipv6PrefixDelegationConfig != { }) '' + [IPv6PrefixDelegation] + ${attrsToSection def.ipv6PrefixDelegationConfig} + '' + + flip concatMapStrings def.ipv6Prefixes (x: '' + [IPv6Prefix] + ${attrsToSection x.ipv6PrefixConfig} + '') + + def.extraConfig; }; unitFiles = listToAttrs (map (name: { @@ -1178,14 +1534,22 @@ in users.users.systemd-network.group = "systemd-network"; systemd.additionalUpstreamSystemUnits = [ - "systemd-networkd.service" "systemd-networkd-wait-online.service" + "systemd-networkd-wait-online.service" + "systemd-networkd.service" + "systemd-networkd.socket" ]; systemd.network.units = mapAttrs' (n: v: nameValuePair "${n}.netdev" (netdevToUnit n v)) cfg.netdevs // mapAttrs' (n: v: nameValuePair "${n}.network" (networkToUnit n v)) cfg.networks; + # systemd-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 = { wantedBy = [ "multi-user.target" ]; + aliases = [ "dbus-org.freedesktop.network1.service" ]; restartTriggers = map (x: x.source) (attrValues unitFiles); # prevent race condition with interface renaming (#39069) requires = [ "systemd-udev-settle.service" ]; diff --git a/nixpkgs/nixos/modules/system/boot/plymouth.nix b/nixpkgs/nixos/modules/system/boot/plymouth.nix index 23fce22366d..55e5b07ed61 100644 --- a/nixpkgs/nixos/modules/system/boot/plymouth.nix +++ b/nixpkgs/nixos/modules/system/boot/plymouth.nix @@ -102,6 +102,8 @@ in 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"]; boot.initrd.extraUtilsCommands = '' copy_bin_and_libs ${pkgs.plymouth}/bin/plymouthd @@ -146,6 +148,7 @@ in # We use `mkAfter` to ensure that LUKS password prompt would be shown earlier than the splash screen. boot.initrd.preLVMCommands = mkAfter '' mkdir -p /etc/plymouth + mkdir -p /run/plymouth ln -s ${configFile} /etc/plymouth/plymouthd.conf ln -s $extraUtils/share/plymouth/plymouthd.defaults /etc/plymouth/plymouthd.defaults ln -s $extraUtils/share/plymouth/logo.png /etc/plymouth/logo.png diff --git a/nixpkgs/nixos/modules/system/boot/resolved.nix b/nixpkgs/nixos/modules/system/boot/resolved.nix index b7aaef575ac..b024f9cf5ee 100644 --- a/nixpkgs/nixos/modules/system/boot/resolved.nix +++ b/nixpkgs/nixos/modules/system/boot/resolved.nix @@ -148,6 +148,7 @@ in systemd.services.systemd-resolved = { wantedBy = [ "multi-user.target" ]; + aliases = [ "dbus-org.freedesktop.resolve1.service" ]; restartTriggers = [ config.environment.etc."systemd/resolved.conf".source ]; }; diff --git a/nixpkgs/nixos/modules/system/boot/stage-1-init.sh b/nixpkgs/nixos/modules/system/boot/stage-1-init.sh index 607aec87f01..0c1be71cf53 100644 --- a/nixpkgs/nixos/modules/system/boot/stage-1-init.sh +++ b/nixpkgs/nixos/modules/system/boot/stage-1-init.sh @@ -144,6 +144,14 @@ for o in $(cat /proc/cmdline); do 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 @@ -370,12 +378,14 @@ mountFS() { mkdir -p "/mnt-root$mountPoint" - # For CIFS mounts, retry a few times before giving up. + # 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 -o "$n" -ge 10 ]; then fail; break; fi + if [ \( "$fsType" != cifs -a "$fsType" != zfs \) -o "$n" -ge 10 ]; then fail; break; fi echo "retrying..." + sleep 1 n=$((n + 1)) done @@ -534,6 +544,14 @@ while read -u 3 mountPoint; do 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" "$mountPoint" "$options" "$fsType" done diff --git a/nixpkgs/nixos/modules/system/boot/stage-1.nix b/nixpkgs/nixos/modules/system/boot/stage-1.nix index dfd158e2d75..6823e12847c 100644 --- a/nixpkgs/nixos/modules/system/boot/stage-1.nix +++ b/nixpkgs/nixos/modules/system/boot/stage-1.nix @@ -36,7 +36,7 @@ let set -euo pipefail declare -A seen - declare -a left + left=() patchelf="${pkgs.buildPackages.patchelf}/bin/patchelf" @@ -48,7 +48,7 @@ let done } - add_needed $1 + add_needed "$1" while [ ''${#left[@]} -ne 0 ]; do next=''${left[0]} @@ -111,20 +111,21 @@ let copy_bin_and_libs ${pkgs.utillinux}/sbin/blkid # Copy dmsetup and lvm. - copy_bin_and_libs ${pkgs.lvm2}/sbin/dmsetup - copy_bin_and_libs ${pkgs.lvm2}/sbin/lvm + copy_bin_and_libs ${getBin pkgs.lvm2}/bin/dmsetup + copy_bin_and_libs ${getBin pkgs.lvm2}/bin/lvm # Add RAID mdadm tool. copy_bin_and_libs ${pkgs.mdadm}/sbin/mdadm copy_bin_and_libs ${pkgs.mdadm}/sbin/mdmon # Copy udev. - copy_bin_and_libs ${udev}/lib/systemd/systemd-udevd - copy_bin_and_libs ${udev}/lib/systemd/systemd-sysctl copy_bin_and_libs ${udev}/bin/udevadm + 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 @@ -235,7 +236,7 @@ let --replace cdrom_id ${extraUtils}/bin/cdrom_id \ --replace ${pkgs.coreutils}/bin/basename ${extraUtils}/bin/basename \ --replace ${pkgs.utillinux}/bin/blkid ${extraUtils}/bin/blkid \ - --replace ${pkgs.lvm2}/sbin ${extraUtils}/bin \ + --replace ${getBin pkgs.lvm2}/bin ${extraUtils}/bin \ --replace ${pkgs.mdadm}/sbin ${extraUtils}/sbin \ --replace ${pkgs.bash}/bin/sh ${extraUtils}/bin/sh \ --replace ${udev} ${extraUtils} @@ -374,7 +375,8 @@ let ) config.boot.initrd.secrets) } - (cd "$tmp" && find . | cpio -H newc -o) | gzip >>"$1" + (cd "$tmp" && find . -print0 | sort -z | cpio -o -H newc -R +0:+0 --reproducible --null) | \ + ${config.boot.initrd.compressor} >> "$1" ''; in @@ -517,8 +519,7 @@ in }; boot.initrd.secrets = mkOption - { internal = true; - default = {}; + { default = {}; type = types.attrsOf (types.nullOr types.path); description = '' @@ -555,15 +556,17 @@ in }; fileSystems = mkOption { - type = with lib.types; loaOf (submodule { + type = with lib.types; attrsOf (submodule { options.neededForBoot = mkOption { default = false; type = types.bool; description = '' - If set, this file system will be mounted in the initial - ramdisk. By default, this applies to the root file system - and to the file system containing - <filename>/nix/store</filename>. + 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: "<filename>${i}</filename>") + )}. ''; }; }); @@ -585,7 +588,7 @@ in { assertion = !config.boot.loader.supportsInitrdSecrets -> all (source: builtins.isPath source || - (builtins.isString source && hasPrefix source builtins.storeDir)) + (builtins.isString source && hasPrefix builtins.storeDir source)) (attrValues config.boot.initrd.secrets); message = '' boot.loader.initrd.secrets values must be unquoted paths when diff --git a/nixpkgs/nixos/modules/system/boot/stage-2-init.sh b/nixpkgs/nixos/modules/system/boot/stage-2-init.sh index d1de7920df9..936077b9df1 100644 --- a/nixpkgs/nixos/modules/system/boot/stage-2-init.sh +++ b/nixpkgs/nixos/modules/system/boot/stage-2-init.sh @@ -169,4 +169,4 @@ exec {logOutFd}>&- {logErrFd}>&- echo "starting systemd..." PATH=/run/current-system/systemd/lib/systemd:@fsPackagesPath@ \ LOCALE_ARCHIVE=/run/current-system/sw/lib/locale/locale-archive \ - exec systemd + exec @systemdExecutable@ diff --git a/nixpkgs/nixos/modules/system/boot/stage-2.nix b/nixpkgs/nixos/modules/system/boot/stage-2.nix index 6b0b4722730..dd6d83ee009 100644 --- a/nixpkgs/nixos/modules/system/boot/stage-2.nix +++ b/nixpkgs/nixos/modules/system/boot/stage-2.nix @@ -10,6 +10,7 @@ let src = ./stage-2-init.sh; shellDebug = "${pkgs.bashInteractive}/bin/bash"; shell = "${pkgs.bash}/bin/bash"; + inherit (config.boot) systemdExecutable; isExecutable = true; inherit (config.nix) readOnlyStore; inherit useHostResolvConf; @@ -72,6 +73,15 @@ in ''; }; + systemdExecutable = mkOption { + default = "systemd"; + type = types.str; + description = '' + The program to execute to start systemd. Typically + <literal>systemd</literal>, which will find systemd in the + PATH. + ''; + }; }; }; diff --git a/nixpkgs/nixos/modules/system/boot/systemd-nspawn.nix b/nixpkgs/nixos/modules/system/boot/systemd-nspawn.nix index 06ea5ee49f7..b450d77429b 100644 --- a/nixpkgs/nixos/modules/system/boot/systemd-nspawn.nix +++ b/nixpkgs/nixos/modules/system/boot/systemd-nspawn.nix @@ -113,9 +113,9 @@ in { config = let units = mapAttrs' (n: v: let nspawnFile = "${n}.nspawn"; in nameValuePair nspawnFile (instanceToUnit nspawnFile v)) cfg; - in + in mkMerge [ - (mkIf (cfg != {}) { + (mkIf (cfg != {}) { environment.etc."systemd/nspawn".source = mkIf (cfg != {}) (generateUnits' false "nspawn" units [] []); }) { @@ -123,7 +123,7 @@ in { # Workaround for https://github.com/NixOS/nixpkgs/pull/67232#issuecomment-531315437 and https://github.com/systemd/systemd/issues/13622 # Once systemd fixes this upstream, we can re-enable -U - systemd.services."systemd-nspawn@".serviceConfig.ExecStart = [ + systemd.services."systemd-nspawn@".serviceConfig.ExecStart = [ "" # deliberately empty. signals systemd to override the ExecStart # Only difference between upstream is that we do not pass the -U flag "${config.systemd.package}/bin/systemd-nspawn --quiet --keep-unit --boot --link-journal=try-guest --network-veth --settings=override --machine=%i" diff --git a/nixpkgs/nixos/modules/system/boot/systemd-unit-options.nix b/nixpkgs/nixos/modules/system/boot/systemd-unit-options.nix index bee21f1a8f3..5addc6f9ca4 100644 --- a/nixpkgs/nixos/modules/system/boot/systemd-unit-options.nix +++ b/nixpkgs/nixos/modules/system/boot/systemd-unit-options.nix @@ -233,7 +233,7 @@ in rec { path = mkOption { default = []; - apply = ps: "${makeBinPath ps}:${makeSearchPathOutput "bin" "sbin" ps}"; + type = with types; listOf (oneOf [ package str ]); description = '' Packages added to the service's <envar>PATH</envar> environment variable. Both the <filename>bin</filename> @@ -378,6 +378,16 @@ in rec { ''; }; + listenDatagrams = mkOption { + default = []; + type = types.listOf types.str; + example = [ "0.0.0.0:993" "/run/my-socket" ]; + description = '' + For each item in this list, a <literal>ListenDatagram</literal> + option in the <literal>[Socket]</literal> section will be created. + ''; + }; + socketConfig = mkOption { default = {}; example = { ListenStream = "/run/my-socket"; }; diff --git a/nixpkgs/nixos/modules/system/boot/systemd.nix b/nixpkgs/nixos/modules/system/boot/systemd.nix index 99892a28115..74d6957678f 100644 --- a/nixpkgs/nixos/modules/system/boot/systemd.nix +++ b/nixpkgs/nixos/modules/system/boot/systemd.nix @@ -25,7 +25,7 @@ let "nss-lookup.target" "nss-user-lookup.target" "time-sync.target" - #"cryptsetup.target" + "cryptsetup.target" "sigpwr.target" "timers.target" "paths.target" @@ -73,7 +73,7 @@ let "systemd-journald.service" "systemd-journal-flush.service" "systemd-journal-catalog-update.service" - "systemd-journald-audit.socket" + ] ++ (optional (!config.boot.isContainer) "systemd-journald-audit.socket") ++ [ "systemd-journald-dev-log.socket" "syslog.socket" @@ -81,10 +81,6 @@ let "systemd-coredump.socket" "systemd-coredump@.service" - # SysV init compatibility. - "systemd-initctl.socket" - "systemd-initctl.service" - # Kernel module loading. "systemd-modules-load.service" "kmod-static-nodes.service" @@ -101,7 +97,7 @@ let "dev-hugepages.mount" "dev-mqueue.mount" "sys-fs-fuse-connections.mount" - "sys-kernel-config.mount" + ] ++ (optional (!config.boot.isContainer) "sys-kernel-config.mount") ++ [ "sys-kernel-debug.mount" # Maintaining state across reboots. @@ -261,7 +257,7 @@ let pkgs.gnused systemd ]; - environment.PATH = config.path; + environment.PATH = "${makeBinPath config.path}:${makeSearchPathOutput "bin" "sbin" config.path}"; } (mkIf (config.preStart != "") { serviceConfig.ExecStartPre = @@ -354,6 +350,7 @@ let [Socket] ${attrsToSection def.socketConfig} ${concatStringsSep "\n" (map (s: "ListenStream=${s}") def.listenStreams)} + ${concatStringsSep "\n" (map (s: "ListenDatagram=${s}") def.listenDatagrams)} ''; }; @@ -749,6 +746,25 @@ in ''; }; + systemd.tmpfiles.packages = mkOption { + type = types.listOf types.package; + default = []; + example = literalExample "[ pkgs.lvm2 ]"; + apply = map getLib; + description = '' + List of packages containing <command>systemd-tmpfiles</command> rules. + + All files ending in .conf found in + <filename><replaceable>pkg</replaceable>/lib/tmpfiles.d</filename> + will be included. + If this folder does not exist or does not contain any files an error will be returned instead. + + If a <filename>lib</filename> output is available, rules are searched there and only there. + If there is no <filename>lib</filename> output it will fall back to <filename>out</filename> + and if that does not exist either, the default output will be used. + ''; + }; + systemd.user.units = mkOption { description = "Definition of systemd per-user units."; default = {}; @@ -818,6 +834,49 @@ in ''; }; + systemd.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. + ''; + }; + + systemd.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". + ''; + }; + + systemd.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. + Valid time units include "ms", "s", "min", "h", "d", and "w". + ''; + }; + + systemd.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. Valid + time units include "ms", "s", "min", "h", "d", and "w". + ''; + }; }; @@ -826,8 +885,13 @@ in config = { warnings = concatLists (mapAttrsToList (name: service: - optional (service.serviceConfig.Type or "" == "oneshot" && service.serviceConfig.Restart or "no" != "no") - "Service ‘${name}.service’ with ‘Type=oneshot’ must have ‘Restart=no’") cfg.services); + let + type = service.serviceConfig.Type or ""; + restart = service.serviceConfig.Restart or "no"; + in optional + (type == "oneshot" && (restart == "always" || restart == "on-success")) + "Service '${name}.service' with 'Type=oneshot' cannot have 'Restart=always' or 'Restart=on-success'") + cfg.services); system.build.units = cfg.units; @@ -839,11 +903,9 @@ in ) ]); passwd = (mkMerge [ - [ "mymachines" ] (mkAfter [ "systemd" ]) ]); group = (mkMerge [ - [ "mymachines" ] (mkAfter [ "systemd" ]) ]); }; @@ -884,6 +946,19 @@ in DefaultIPAccounting=yes ''} DefaultLimitCORE=infinity + ${optionalString (config.systemd.watchdog.device != null) '' + WatchdogDevice=${config.systemd.watchdog.device} + ''} + ${optionalString (config.systemd.watchdog.runtimeTime != null) '' + RuntimeWatchdogSec=${config.systemd.watchdog.runtimeTime} + ''} + ${optionalString (config.systemd.watchdog.rebootTime != null) '' + RebootWatchdogSec=${config.systemd.watchdog.rebootTime} + ''} + ${optionalString (config.systemd.watchdog.kexecTime != null) '' + KExecWatchdogSec=${config.systemd.watchdog.kexecTime} + ''} + ${config.systemd.extraConfig} ''; @@ -931,24 +1006,20 @@ in "sysctl.d/50-coredump.conf".source = "${systemd}/example/sysctl.d/50-coredump.conf"; "sysctl.d/50-default.conf".source = "${systemd}/example/sysctl.d/50-default.conf"; - "tmpfiles.d/00-nixos.conf".text = '' - # This file is created automatically and should not be modified. - # Please change the option ‘systemd.tmpfiles.rules’ instead. - - ${concatStringsSep "\n" cfg.tmpfiles.rules} - ''; - - "tmpfiles.d/home.conf".source = "${systemd}/example/tmpfiles.d/home.conf"; - "tmpfiles.d/journal-nocow.conf".source = "${systemd}/example/tmpfiles.d/journal-nocow.conf"; - "tmpfiles.d/portables.conf".source = "${systemd}/example/tmpfiles.d/portables.conf"; - "tmpfiles.d/static-nodes-permissions.conf".source = "${systemd}/example/tmpfiles.d/static-nodes-permissions.conf"; - "tmpfiles.d/systemd.conf".source = "${systemd}/example/tmpfiles.d/systemd.conf"; - "tmpfiles.d/systemd-nologin.conf".source = "${systemd}/example/tmpfiles.d/systemd-nologin.conf"; - "tmpfiles.d/systemd-nspawn.conf".source = "${systemd}/example/tmpfiles.d/systemd-nspawn.conf"; - "tmpfiles.d/systemd-tmp.conf".source = "${systemd}/example/tmpfiles.d/systemd-tmp.conf"; - "tmpfiles.d/tmp.conf".source = "${systemd}/example/tmpfiles.d/tmp.conf"; - "tmpfiles.d/var.conf".source = "${systemd}/example/tmpfiles.d/var.conf"; - "tmpfiles.d/x11.conf".source = "${systemd}/example/tmpfiles.d/x11.conf"; + "tmpfiles.d".source = (pkgs.symlinkJoin { + name = "tmpfiles.d"; + paths = map (p: p + "/lib/tmpfiles.d") cfg.tmpfiles.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.targets; + }) + "/*"; "systemd/system-generators" = { source = hooks "generators" cfg.generators; }; "systemd/system-shutdown" = { source = hooks "shutdown" cfg.shutdown; }; @@ -969,6 +1040,36 @@ in unitConfig.X-StopOnReconfiguration = true; }; + 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 + + ln -s "${systemd}/example/tmpfiles.d/home.conf" + ln -s "${systemd}/example/tmpfiles.d/journal-nocow.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.tmpfiles.rules} + ''; + }) + ]; + systemd.units = mapAttrs' (n: v: nameValuePair "${n}.path" (pathToUnit n v)) cfg.paths // mapAttrs' (n: v: nameValuePair "${n}.service" (serviceToUnit n v)) cfg.services diff --git a/nixpkgs/nixos/modules/system/boot/timesyncd.nix b/nixpkgs/nixos/modules/system/boot/timesyncd.nix index 9e2f36ca01f..35fb5578b07 100644 --- a/nixpkgs/nixos/modules/system/boot/timesyncd.nix +++ b/nixpkgs/nixos/modules/system/boot/timesyncd.nix @@ -41,6 +41,7 @@ with lib; systemd.services.systemd-timesyncd = { wantedBy = [ "sysinit.target" ]; + aliases = [ "dbus-org.freedesktop.timesync1.service" ]; restartTriggers = [ config.environment.etc."systemd/timesyncd.conf".source ]; }; diff --git a/nixpkgs/nixos/modules/system/boot/tmp.nix b/nixpkgs/nixos/modules/system/boot/tmp.nix index 5bf5e2eb2ec..26eb172210e 100644 --- a/nixpkgs/nixos/modules/system/boot/tmp.nix +++ b/nixpkgs/nixos/modules/system/boot/tmp.nix @@ -36,4 +36,4 @@ with lib; }; -}
\ No newline at end of file +} |