push sheeet
Some checks failed
Periodic Merges (6h) / master → staging-nixos (push) Failing after 12m50s
Periodic Merges (6h) / master → staging-next (push) Failing after 12m54s
Periodic Merges (24h) / merge-base(master,staging) → haskell-updates (push) Failing after 11m54s
Periodic Merges (6h) / staging-next → staging (push) Failing after 12m13s
Periodic Merges (24h) / staging-next-25.05 → staging-25.05 (push) Failing after 13m24s
Periodic Merges (24h) / release-25.05 → staging-next-25.05 (push) Failing after 14m28s

This commit is contained in:
Dark Steveneq
2025-10-09 14:15:47 +02:00
commit 646b892680
49168 changed files with 5897842 additions and 0 deletions

View File

@@ -0,0 +1,311 @@
{
config,
lib,
pkgs,
...
}:
let
cfg = config.system.autoUpgrade;
in
{
options = {
system.autoUpgrade = {
enable = lib.mkOption {
type = lib.types.bool;
default = false;
description = ''
Whether to periodically upgrade NixOS to the latest
version. If enabled, a systemd timer will run
`nixos-rebuild switch --upgrade` once a
day.
'';
};
operation = lib.mkOption {
type = lib.types.enum [
"switch"
"boot"
];
default = "switch";
example = "boot";
description = ''
Whether to run
`nixos-rebuild switch --upgrade` or run
`nixos-rebuild boot --upgrade`
'';
};
flake = lib.mkOption {
type = lib.types.nullOr lib.types.str;
default = null;
example = "github:kloenk/nix";
description = ''
The Flake URI of the NixOS configuration to build.
Disables the option {option}`system.autoUpgrade.channel`.
'';
};
channel = lib.mkOption {
type = lib.types.nullOr lib.types.str;
default = null;
example = "https://nixos.org/channels/nixos-14.12-small";
description = ''
The URI of the NixOS channel to use for automatic
upgrades. By default, this is the channel set using
{command}`nix-channel` (run `nix-channel --list`
to see the current value).
'';
};
upgrade = lib.mkOption {
type = lib.types.bool;
default = true;
description = ''
Disable adding the `--upgrade` parameter when `channel`
is not set, such as when upgrading to the latest version
of a flake honouring its lockfile.
'';
};
flags = lib.mkOption {
type = lib.types.listOf lib.types.str;
default = [ ];
example = [
"-I"
"stuff=/home/alice/nixos-stuff"
"--option"
"extra-binary-caches"
"http://my-cache.example.org/"
];
description = ''
Any additional flags passed to {command}`nixos-rebuild`.
If you are using flakes and use a local repo you can add
{command}`[ "--update-input" "nixpkgs" "--commit-lock-file" ]`
to update nixpkgs.
'';
};
dates = lib.mkOption {
type = lib.types.str;
default = "04:40";
example = "daily";
description = ''
How often or when upgrade occurs. For most desktop and server systems
a sufficient upgrade frequency is once a day.
The format is described in
{manpage}`systemd.time(7)`.
'';
};
allowReboot = lib.mkOption {
default = false;
type = lib.types.bool;
description = ''
Reboot the system into the new generation instead of a switch
if the new generation uses a different kernel, kernel modules
or initrd than the booted system.
See {option}`rebootWindow` for configuring the times at which a reboot is allowed.
'';
};
randomizedDelaySec = lib.mkOption {
default = "0";
type = lib.types.str;
example = "45min";
description = ''
Add a randomized delay before each automatic upgrade.
The delay will be chosen between zero and this value.
This value must be a time span in the format specified by
{manpage}`systemd.time(7)`
'';
};
fixedRandomDelay = lib.mkOption {
default = false;
type = lib.types.bool;
example = true;
description = ''
Make the randomized delay consistent between runs.
This reduces the jitter between automatic upgrades.
See {option}`randomizedDelaySec` for configuring the randomized delay.
'';
};
rebootWindow = lib.mkOption {
description = ''
Define a lower and upper time value (in HH:MM format) which
constitute a time window during which reboots are allowed after an upgrade.
This option only has an effect when {option}`allowReboot` is enabled.
The default value of `null` means that reboots are allowed at any time.
'';
default = null;
example = {
lower = "01:00";
upper = "05:00";
};
type =
with lib.types;
nullOr (submodule {
options = {
lower = lib.mkOption {
description = "Lower limit of the reboot window";
type = lib.types.strMatching "[[:digit:]]{2}:[[:digit:]]{2}";
example = "01:00";
};
upper = lib.mkOption {
description = "Upper limit of the reboot window";
type = lib.types.strMatching "[[:digit:]]{2}:[[:digit:]]{2}";
example = "05:00";
};
};
});
};
persistent = lib.mkOption {
default = true;
type = lib.types.bool;
example = false;
description = ''
Takes a boolean argument. If true, the time when the service
unit was last triggered is stored on disk. When the timer is
activated, the service unit is triggered immediately if it
would have been triggered at least once during the time when
the timer was inactive. Such triggering is nonetheless
subject to the delay imposed by RandomizedDelaySec=. This is
useful to catch up on missed runs of the service when the
system was powered down.
'';
};
};
};
config = lib.mkIf cfg.enable {
assertions = [
{
assertion = !((cfg.channel != null) && (cfg.flake != null));
message = ''
The options 'system.autoUpgrade.channel' and 'system.autoUpgrade.flake' cannot both be set.
'';
}
];
system.autoUpgrade.flags = (
if cfg.flake == null then
[ "--no-build-output" ]
++ lib.optionals (cfg.channel != null) [
"-I"
"nixpkgs=${cfg.channel}/nixexprs.tar.xz"
]
else
[
"--refresh"
"--flake ${cfg.flake}"
]
);
systemd.services.nixos-upgrade = {
description = "NixOS Upgrade";
restartIfChanged = false;
unitConfig.X-StopOnRemoval = false;
serviceConfig.Type = "oneshot";
environment =
config.nix.envVars
// {
inherit (config.environment.sessionVariables) NIX_PATH;
HOME = "/root";
}
// config.networking.proxy.envVars;
path = with pkgs; [
coreutils
gnutar
xz.bin
gzip
gitMinimal
config.nix.package.out
config.programs.ssh.package
];
script =
let
nixos-rebuild = "${config.system.build.nixos-rebuild}/bin/nixos-rebuild";
date = "${pkgs.coreutils}/bin/date";
readlink = "${pkgs.coreutils}/bin/readlink";
shutdown = "${config.systemd.package}/bin/shutdown";
upgradeFlag = lib.optional (cfg.channel == null && cfg.upgrade) "--upgrade";
in
if cfg.allowReboot then
''
${nixos-rebuild} boot ${toString (cfg.flags ++ upgradeFlag)}
booted="$(${readlink} /run/booted-system/{initrd,kernel,kernel-modules})"
built="$(${readlink} /nix/var/nix/profiles/system/{initrd,kernel,kernel-modules})"
${lib.optionalString (cfg.rebootWindow != null) ''
current_time="$(${date} +%H:%M)"
lower="${cfg.rebootWindow.lower}"
upper="${cfg.rebootWindow.upper}"
if [[ "''${lower}" < "''${upper}" ]]; then
if [[ "''${current_time}" > "''${lower}" ]] && \
[[ "''${current_time}" < "''${upper}" ]]; then
do_reboot="true"
else
do_reboot="false"
fi
else
# lower > upper, so we are crossing midnight (e.g. lower=23h, upper=6h)
# we want to reboot if cur > 23h or cur < 6h
if [[ "''${current_time}" < "''${upper}" ]] || \
[[ "''${current_time}" > "''${lower}" ]]; then
do_reboot="true"
else
do_reboot="false"
fi
fi
''}
if [ "''${booted}" = "''${built}" ]; then
${nixos-rebuild} ${cfg.operation} ${toString cfg.flags}
${lib.optionalString (cfg.rebootWindow != null) ''
elif [ "''${do_reboot}" != true ]; then
echo "Outside of configured reboot window, skipping."
''}
else
${shutdown} -r +1
fi
''
else
''
${nixos-rebuild} ${cfg.operation} ${toString (cfg.flags ++ upgradeFlag)}
'';
startAt = cfg.dates;
after = [ "network-online.target" ];
wants = [ "network-online.target" ];
};
systemd.timers.nixos-upgrade = {
timerConfig = {
RandomizedDelaySec = cfg.randomizedDelaySec;
FixedRandomDelay = cfg.fixedRandomDelay;
Persistent = cfg.persistent;
};
};
};
}

View File

@@ -0,0 +1,40 @@
{
config,
lib,
pkgs,
...
}:
let
cfg = config.boot.bcache;
in
{
options.boot.bcache.enable = lib.mkEnableOption "bcache mount support" // {
default = true;
example = false;
};
options.boot.initrd.services.bcache.enable = lib.mkEnableOption "bcache support in the initrd" // {
description = ''
*This will only be used when systemd is used in stage 1.*
Whether to enable bcache support in the initrd.
'';
default = config.boot.initrd.systemd.enable && config.boot.bcache.enable;
defaultText = lib.literalExpression "config.boot.initrd.systemd.enable && config.boot.bcache.enable";
};
config = lib.mkIf cfg.enable {
environment.systemPackages = [ pkgs.bcache-tools ];
services.udev.packages = [ pkgs.bcache-tools ];
boot.initrd.extraUdevRulesCommands = lib.mkIf (!config.boot.initrd.systemd.enable) ''
cp -v ${pkgs.bcache-tools}/lib/udev/rules.d/*.rules $out/
'';
boot.initrd.services.udev = lib.mkIf config.boot.initrd.services.bcache.enable {
packages = [ pkgs.bcache-tools ];
binPackages = [ pkgs.bcache-tools ];
};
};
}

View File

@@ -0,0 +1,96 @@
{
config,
lib,
pkgs,
...
}:
with lib;
let
cpupower = config.boot.kernelPackages.cpupower;
cfg = config.powerManagement;
in
{
###### interface
options.powerManagement = {
# TODO: This should be aliased to powerManagement.cpufreq.governor.
# https://github.com/NixOS/nixpkgs/pull/53041#commitcomment-31825338
cpuFreqGovernor = mkOption {
type = types.nullOr types.str;
default = null;
example = "ondemand";
description = ''
Configure the governor used to regulate the frequency of the
available CPUs. By default, the kernel configures the
performance governor, although this may be overwritten in your
hardware-configuration.nix file.
Often used values: "ondemand", "powersave", "performance"
'';
};
cpufreq = {
max = mkOption {
type = types.nullOr types.ints.unsigned;
default = null;
example = 2200000;
description = ''
The maximum frequency the CPU will use. Defaults to the maximum possible.
'';
};
min = mkOption {
type = types.nullOr types.ints.unsigned;
default = null;
example = 800000;
description = ''
The minimum frequency the CPU will use.
'';
};
};
};
###### implementation
config =
let
governorEnable = cfg.cpuFreqGovernor != null;
maxEnable = cfg.cpufreq.max != null;
minEnable = cfg.cpufreq.min != null;
enable = !config.boot.isContainer && (governorEnable || maxEnable || minEnable);
in
mkIf enable {
boot.kernelModules = optional governorEnable "cpufreq_${cfg.cpuFreqGovernor}";
environment.systemPackages = [ cpupower ];
systemd.services.cpufreq = {
description = "CPU Frequency Setup";
after = [ "systemd-modules-load.service" ];
wantedBy = [ "multi-user.target" ];
path = [
cpupower
pkgs.kmod
];
unitConfig.ConditionVirtualization = false;
serviceConfig = {
Type = "oneshot";
RemainAfterExit = "yes";
ExecStart =
"${cpupower}/bin/cpupower frequency-set "
+ optionalString governorEnable "--governor ${cfg.cpuFreqGovernor} "
+ optionalString maxEnable "--max ${toString cfg.cpufreq.max} "
+ optionalString minEnable "--min ${toString cfg.cpufreq.min} ";
SuccessExitStatus = "0 237";
};
};
};
}

View File

@@ -0,0 +1,131 @@
{ config, lib, ... }:
with lib;
let
fileSystems = config.system.build.fileSystems ++ config.swapDevices;
encDevs = filter (dev: dev.encrypted.enable) fileSystems;
# With scripted initrd, devices with a keyFile have to be opened
# late, after file systems are mounted, because that could be where
# the keyFile is located. With systemd initrd, each individual
# systemd-cryptsetup@ unit has RequiresMountsFor= to delay until all
# the mount units for the key file are done; i.e. no special
# treatment is needed.
lateEncDevs =
if config.boot.initrd.systemd.enable then
{ }
else
filter (dev: dev.encrypted.keyFile != null) encDevs;
earlyEncDevs =
if config.boot.initrd.systemd.enable then
encDevs
else
filter (dev: dev.encrypted.keyFile == null) encDevs;
anyEncrypted = foldr (j: v: v || j.encrypted.enable) false encDevs;
encryptedFSOptions = {
options.encrypted = {
enable = mkOption {
default = false;
type = types.bool;
description = "The block device is backed by an encrypted one, adds this device as a initrd luks entry.";
};
blkDev = mkOption {
default = null;
example = "/dev/sda1";
type = types.nullOr types.str;
description = "Location of the backing encrypted device.";
};
label = mkOption {
default = null;
example = "rootfs";
type = types.nullOr types.str;
description = "Label of the unlocked encrypted device. Set `fileSystems.<name?>.device` to `/dev/mapper/<label>` to mount the unlocked device.";
};
keyFile = mkOption {
default = null;
example = "/mnt-root/root/.swapkey";
type = types.nullOr types.str;
description = ''
Path to a keyfile used to unlock the backing encrypted
device. When systemd stage 1 is not enabled, at the time
this keyfile is accessed, the `neededForBoot` filesystems
(see `utils.fsNeededForBoot`) will have been mounted under
`/mnt-root`, so the keyfile path should usually start with
"/mnt-root/". When systemd stage 1 is enabled,
`fsNeededForBoot` file systems will be mounted as needed
under `/sysroot`, and the keyfile will not be accessed until
its requisite mounts are done.
'';
};
};
};
in
{
options = {
fileSystems = mkOption {
type = with lib.types; attrsOf (submodule encryptedFSOptions);
};
swapDevices = mkOption {
type = with lib.types; listOf (submodule encryptedFSOptions);
};
};
config = mkIf anyEncrypted {
assertions = concatMap (dev: [
{
assertion = dev.encrypted.label != null;
message = ''
The filesystem for ${dev.mountPoint} has encrypted.enable set to true, but no encrypted.label set
'';
}
{
assertion =
config.boot.initrd.systemd.enable
-> (
dev.encrypted.keyFile == null
|| !lib.any (x: lib.hasPrefix x dev.encrypted.keyFile) [
"/mnt-root"
"$targetRoot"
]
);
message = ''
Bad use of '/mnt-root' or '$targetRoot` in 'keyFile'.
When 'boot.initrd.systemd.enable' is enabled, file systems
are mounted at '/sysroot' instead of '/mnt-root'.
'';
}
]) encDevs;
boot.initrd = {
luks = {
devices = builtins.listToAttrs (
map (dev: {
name = dev.encrypted.label;
value = {
device = dev.encrypted.blkDev;
inherit (dev.encrypted) keyFile;
};
}) earlyEncDevs
);
forceLuksSupportInInitrd = true;
};
# TODO: systemd stage 1
postMountCommands = lib.mkIf (!config.boot.initrd.systemd.enable) (
concatMapStrings (
dev:
"cryptsetup luksOpen --key-file ${dev.encrypted.keyFile} ${dev.encrypted.blkDev} ${dev.encrypted.label};\n"
) lateEncDevs
);
};
};
}

View File

@@ -0,0 +1,623 @@
{
config,
lib,
pkgs,
utils,
...
}@moduleArgs:
let
inherit (lib)
any
attrValues
concatMapStrings
concatMapStringsSep
concatStringsSep
elem
filter
flip
head
literalExpression
mkDefault
mkEnableOption
mkIf
mkMerge
mkOption
optional
optionalAttrs
optionalString
toposort
types
;
inherit (utils) fsBefore;
# https://wiki.archlinux.org/index.php/fstab#Filepath_spaces
escape = string: builtins.replaceStrings [ " " "\t" ] [ "\\040" "\\011" ] string;
# A list of attrnames is coerced into an attrset of bools by
# setting the values to true.
attrNamesToTrue = types.coercedTo (types.listOf types.str) (
enabledList: lib.genAttrs enabledList (_attrName: true)
) (types.attrsOf types.bool);
addCheckDesc =
desc: elemType: check:
types.addCheck elemType check // { description = "${elemType.description} (with check: ${desc})"; };
isNonEmpty = s: (builtins.match "[ \t\n]*" s) == null;
nonEmptyStr = addCheckDesc "non-empty" types.str isNonEmpty;
fileSystems' = toposort fsBefore (attrValues config.fileSystems);
fileSystems =
if fileSystems' ? result then
# use topologically sorted fileSystems everywhere
fileSystems'.result
else
# the assertion below will catch this,
# but we fall back to the original order
# anyway so that other modules could check
# their assertions too
(attrValues config.fileSystems);
specialFSTypes = [
"proc"
"sysfs"
"tmpfs"
"ramfs"
"devtmpfs"
"devpts"
];
nonEmptyWithoutTrailingSlash = addCheckDesc "non-empty without trailing slash" types.str (
s: isNonEmpty s && (builtins.match ".+/" s) == null
);
coreFileSystemOpts =
{ name, config, ... }:
{
options = {
enable = mkEnableOption "the filesystem mount" // {
default = true;
};
mountPoint = mkOption {
example = "/mnt/usb";
type = nonEmptyWithoutTrailingSlash;
default = name;
description = ''
Location where the file system will be mounted.
This is called `mountpoint` in {manpage}`mount(8)` and `fs_file` in {manpage}`fstab(5)`
'';
};
stratis.poolUuid = mkOption {
type = types.uniq (types.nullOr types.str);
description = ''
UUID of the stratis pool that the fs is located in
This is only relevant if you are using [stratis](https://stratis-storage.github.io/).
'';
example = "04c68063-90a5-4235-b9dd-6180098a20d9";
default = null;
};
device = mkOption {
default = null;
example = "/dev/sda";
type = types.nullOr nonEmptyStr;
description = ''
The device as passed to `mount`.
This can be any of:
- a filename of a block special device such as `/dev/sdc3`
- a tag such as `UUID=fdd68895-c307-4549-8c9c-90e44c71f5b7`
- (for bind mounts only) the source path
- something else depending on the {option}`fsType`. For example, `nfs` device may look like `knuth.cwi.nl:/dir`
This is called `device` in {manpage}`mount(8)` and `fs_spec` in {manpage}`fstab(5)`.
'';
};
fsType = mkOption {
default = "auto";
example = "ext3";
type = nonEmptyStr;
description = ''
Type of the file system.
This is the `fstype` passed to `-t` in the {manpage}`mount(8)` command, and is called `fs_vfstype` in {manpage}`fstab(5)`.
'';
};
options = mkOption {
default = [ "defaults" ];
example = [ "data=journal" ];
description = ''
Options used to mount the file system.
This is called `options` in {manpage}`mount(8)` and `fs_mntops` in {manpage}`fstab(5)`
Some options that can be used for all mounts are documented in {manpage}`mount(8)` under `FILESYSTEM-INDEPENDENT MOUNT OPTIONS`.
Options that systemd understands are documented in {manpage}`systemd.mount(5)` under `FSTAB`.
Each filesystem supports additional options, see the docs for that filesystem.
'';
type = types.nonEmptyListOf nonEmptyStr;
};
depends = mkOption {
default = [ ];
example = [ "/persist" ];
type = types.listOf nonEmptyWithoutTrailingSlash;
description = ''
List of paths that should be mounted before this one. This filesystem's
{option}`device` and {option}`mountPoint` are always
checked and do not need to be included explicitly. If a path is added
to this list, any other filesystem whose mount point is a parent of
the path will be mounted before this filesystem. The paths do not need
to actually be the {option}`mountPoint` of some other filesystem.
This is useful for mounts which require keys and/or configuration files residing on another filesystem.
'';
};
};
config = {
device = mkIf (elem config.fsType specialFSTypes) (mkDefault config.fsType);
};
};
fileSystemOpts =
{ config, ... }:
{
options = {
label = mkOption {
default = null;
example = "root-partition";
type = types.nullOr nonEmptyStr;
description = ''
Label of the device. This simply sets {option}`device` to
`/dev/disk/by-id/''${label}`. Note that devices will not
have a label unless they contain a filesystem which
supports labels, such as ext4 or fat32.
'';
};
autoFormat = mkOption {
default = false;
type = types.bool;
description = ''
If the device does not currently contain a filesystem (as
determined by {command}`blkid`), then automatically
format it with the filesystem type specified in
{option}`fsType`. Use with caution.
'';
};
formatOptions = mkOption {
visible = false;
type = types.unspecified;
default = null;
};
autoResize = mkOption {
default = false;
type = types.bool;
description = ''
If set, the filesystem is grown to its maximum size before
being mounted. (This is typically the size of the containing
partition.) This is currently only supported for ext2/3/4
filesystems that are mounted during early boot.
'';
};
noCheck = mkOption {
default = false;
type = types.bool;
description = "Disable running fsck on this filesystem.";
};
};
config.device = mkIf (config.label != null) (mkDefault "/dev/disk/by-label/${escape config.label}");
config.options =
let
inInitrd = utils.fsNeededForBoot config;
in
mkMerge [
(mkIf config.autoResize [ "x-systemd.growfs" ])
(mkIf config.autoFormat [ "x-systemd.makefs" ])
(mkIf (utils.fsNeededForBoot config) [ "x-initrd.mount" ])
(mkIf
# With scripted stage 1, depends is implemented by sorting 'config.system.build.fileSystems'
(lib.length config.depends > 0 && (inInitrd -> moduleArgs.config.boot.initrd.systemd.enable))
(map (x: "x-systemd.requires-mounts-for=${optionalString inInitrd "/sysroot"}${x}") config.depends)
)
];
};
# Makes sequence of `specialMount device mountPoint options fsType` commands.
# `systemMount` should be defined in the sourcing script.
makeSpecialMounts =
mounts:
pkgs.writeText "mounts.sh" (
concatMapStringsSep "\n" (mount: ''
specialMount "${mount.device}" "${mount.mountPoint}" "${concatStringsSep "," mount.options}" "${mount.fsType}"
'') mounts
);
makeFstabEntries =
let
fsToSkipCheck = [
"none"
"auto"
"overlay"
"iso9660"
"bindfs"
"udf"
"btrfs"
"zfs"
"tmpfs"
"bcachefs"
"nfs"
"nfs4"
"nilfs2"
"vboxsf"
"squashfs"
"glusterfs"
"apfs"
"9p"
"cifs"
"prl_fs"
"vmhgfs"
]
++ lib.optionals (!config.boot.initrd.checkJournalingFS) [
"ext3"
"ext4"
"reiserfs"
"xfs"
"jfs"
"f2fs"
];
isBindMount = fs: builtins.elem "bind" fs.options;
skipCheck =
fs: fs.noCheck || fs.device == "none" || builtins.elem fs.fsType fsToSkipCheck || isBindMount fs;
in
fstabFileSystems:
{ }:
concatMapStrings (
fs:
(
if fs.device != null then
escape fs.device
else
throw "No device specified for mount point ${fs.mountPoint}."
)
+ " "
+ escape fs.mountPoint
+ " "
+ fs.fsType
+ " "
+ escape (builtins.concatStringsSep "," fs.options)
+ " 0 "
+ (
if skipCheck fs then
"0"
else if fs.mountPoint == "/" then
"1"
else
"2"
)
+ "\n"
) fstabFileSystems;
initrdFstab = pkgs.writeText "initrd-fstab" (
makeFstabEntries (filter utils.fsNeededForBoot fileSystems) { }
);
in
{
###### interface
options = {
fileSystems = mkOption {
default = { };
example = literalExpression ''
{
"/".device = "/dev/hda1";
"/data" = {
device = "/dev/hda2";
fsType = "ext3";
options = [ "data=journal" ];
};
"/bigdisk".label = "bigdisk";
}
'';
type = types.attrsOf (
types.submodule [
coreFileSystemOpts
fileSystemOpts
]
);
apply = lib.filterAttrs (_: fs: fs.enable);
description = ''
The file systems to be mounted. It must include an entry for
the root directory (`mountPoint = "/"`). Each
entry in the list is an attribute set with the following fields:
`mountPoint`, `device`,
`fsType` (a file system type recognised by
{command}`mount`; defaults to
`"auto"`), and `options`
(the mount options passed to {command}`mount` using the
{option}`-o` flag; defaults to `[ "defaults" ]`).
Instead of specifying `device`, you can also
specify a volume label (`label`) for file
systems that support it, such as ext2/ext3 (see {command}`mke2fs -L`).
'';
};
system.fsPackages = mkOption {
internal = true;
default = [ ];
description = "Packages supplying file system mounters and checkers.";
};
boot.supportedFilesystems = mkOption {
default = { };
example = literalExpression ''
{
btrfs = true;
zfs = lib.mkForce false;
}
'';
type = attrNamesToTrue;
description = ''
Names of supported filesystem types, or an attribute set of file system types
and their state. The set form may be used together with `lib.mkForce` to
explicitly disable support for specific filesystems, e.g. to disable ZFS
with an unsupported kernel.
'';
};
boot.specialFileSystems = mkOption {
default = { };
type = types.attrsOf (types.submodule coreFileSystemOpts);
apply = lib.filterAttrs (_: fs: fs.enable);
internal = true;
description = ''
Special filesystems that are mounted very early during boot.
'';
};
boot.devSize = mkOption {
default = "5%";
example = "32m";
type = types.str;
description = ''
Size limit for the /dev tmpfs. Look at {manpage}`mount(8)`, tmpfs size option,
for the accepted syntax.
'';
};
boot.devShmSize = mkOption {
default = "50%";
example = "256m";
type = types.str;
description = ''
Size limit for the /dev/shm tmpfs. Look at {manpage}`mount(8)`, tmpfs size option,
for the accepted syntax.
'';
};
boot.runSize = mkOption {
default = "25%";
example = "256m";
type = types.str;
description = ''
Size limit for the /run tmpfs. Look at {manpage}`mount(8)`, tmpfs size option,
for the accepted syntax.
'';
};
};
###### implementation
config = {
assertions =
let
ls = sep: concatMapStringsSep sep (x: x.mountPoint);
resizableFSes = [
"ext3"
"ext4"
"btrfs"
"xfs"
];
notAutoResizable = fs: fs.autoResize && !(builtins.elem fs.fsType resizableFSes);
in
[
{
assertion = !(fileSystems' ? cycle);
message = "The fileSystems option can't be topologically sorted: mountpoint dependency path ${ls " -> " fileSystems'.cycle} loops to ${ls ", " fileSystems'.loops}";
}
{
assertion = !(any notAutoResizable fileSystems);
message =
let
fs = head (filter notAutoResizable fileSystems);
in
''
Mountpoint '${fs.mountPoint}': 'autoResize = true' is not supported for 'fsType = "${fs.fsType}"'
${optionalString (fs.fsType == "auto") "fsType has to be explicitly set and"}
only the following support it: ${lib.concatStringsSep ", " resizableFSes}.
'';
}
{
assertion = !(any (fs: fs.formatOptions != null) fileSystems);
message = ''
'fileSystems.<name>.formatOptions' has been removed, since
systemd-makefs does not support any way to provide formatting
options.
'';
}
]
++ lib.map (fs: {
assertion = fs.label != null -> fs.device == "/dev/disk/by-label/${escape fs.label}";
message = ''
The filesystem with mount point ${fs.mountPoint} has its label and device set to inconsistent values:
label: ${toString fs.label}
device: ${toString fs.device}
'filesystems.<name>.label' and 'filesystems.<name>.device' are mutually exclusive. Please set only one.
'';
}) fileSystems;
# Export for use in other modules
system.build.fileSystems = fileSystems;
system.build.earlyMountScript = makeSpecialMounts (toposort fsBefore (
attrValues config.boot.specialFileSystems
)).result;
boot.supportedFilesystems = map (fs: fs.fsType) fileSystems;
# Add the mount helpers to the system path so that `mount' can find them.
system.fsPackages = [ pkgs.dosfstools ];
environment.systemPackages = config.system.fsPackages;
environment.etc.fstab.text =
let
swapOptions =
sw:
concatStringsSep "," (
sw.options
++ optional (sw.priority != null) "pri=${toString sw.priority}"
++
optional (sw.discardPolicy != null)
"discard${optionalString (sw.discardPolicy != "both") "=${toString sw.discardPolicy}"}"
);
in
''
# This is a generated file. Do not edit!
#
# To make changes, edit the fileSystems and swapDevices NixOS options
# in your /etc/nixos/configuration.nix file.
#
# <file system> <mount point> <type> <options> <dump> <pass>
# Filesystems.
${makeFstabEntries fileSystems { }}
${optionalString (config.swapDevices != [ ]) "# Swap devices."}
${flip concatMapStrings config.swapDevices (sw: "${sw.realDevice} none swap ${swapOptions sw}\n")}
'';
boot.initrd.systemd.storePaths = [ initrdFstab ];
boot.initrd.systemd.managerEnvironment.SYSTEMD_SYSROOT_FSTAB = initrdFstab;
boot.initrd.systemd.services.initrd-parse-etc.environment.SYSTEMD_SYSROOT_FSTAB = initrdFstab;
# Provide a target that pulls in all filesystems.
systemd.targets.fs = {
description = "All File Systems";
wants = [
"local-fs.target"
"remote-fs.target"
];
};
systemd.tmpfiles.rules = [
"d /run/keys 0750 root ${toString config.ids.gids.keys}"
"z /run/keys 0750 root ${toString config.ids.gids.keys}"
];
# Sync mount options with systemd's src/core/mount-setup.c: mount_table.
boot.specialFileSystems = {
# To hold secrets that shouldn't be written to disk
"/run/keys" = {
fsType = "ramfs";
options = [
"nosuid"
"nodev"
"mode=750"
];
};
}
// optionalAttrs (!config.boot.isContainer) {
# systemd-nspawn populates /sys by itself, and remounting it causes all
# kinds of weird issues (most noticeably, waiting for host disk device
# nodes).
"/sys" = {
fsType = "sysfs";
options = [
"nosuid"
"noexec"
"nodev"
];
};
}
// optionalAttrs (!config.boot.isNspawnContainer) {
"/proc" = {
fsType = "proc";
options = [
"nosuid"
"noexec"
"nodev"
];
};
"/run" = {
fsType = "tmpfs";
options = [
"nosuid"
"nodev"
"strictatime"
"mode=755"
"size=${config.boot.runSize}"
];
};
"/dev" = {
fsType = "devtmpfs";
options = [
"nosuid"
"strictatime"
"mode=755"
"size=${config.boot.devSize}"
];
};
"/dev/shm" = {
fsType = "tmpfs";
options = [
"nosuid"
"nodev"
"strictatime"
"mode=1777"
"size=${config.boot.devShmSize}"
];
};
"/dev/pts" = {
fsType = "devpts";
options = [
"nosuid"
"noexec"
"mode=620"
"ptmxmode=0666"
"gid=${toString config.ids.gids.tty}"
];
};
};
};
}

View File

@@ -0,0 +1,27 @@
{
config,
lib,
pkgs,
...
}:
with lib;
let
inInitrd = config.boot.initrd.supportedFilesystems.apfs or false;
in
{
config = mkIf (config.boot.supportedFilesystems.apfs or false) {
system.fsPackages = [ pkgs.apfsprogs ];
boot.extraModulePackages = [ config.boot.kernelPackages.apfs ];
boot.initrd.kernelModules = mkIf inInitrd [ "apfs" ];
# Don't copy apfsck into the initramfs since it does not support repairing the filesystem
};
}

View File

@@ -0,0 +1,398 @@
{
config,
lib,
pkgs,
utils,
...
}:
let
cfg = config.boot.bcachefs;
cfgScrub = config.services.bcachefs.autoScrub;
bootFs = lib.filterAttrs (
n: fs: (fs.fsType == "bcachefs") && (utils.fsNeededForBoot fs)
) config.fileSystems;
commonFunctions = ''
prompt() {
local name="$1"
printf "enter passphrase for $name: "
}
tryUnlock() {
local name="$1"
local path="$2"
local success=false
local target
local uuid=$(echo -n $path | sed -e 's,UUID=\(.*\),\1,g')
printf "waiting for device to appear $path"
for try in $(seq 10); do
if [ -e $path ]; then
target=$(readlink -f $path)
success=true
break
else
target=$(blkid --uuid $uuid)
if [ $? == 0 ]; then
success=true
break
fi
fi
echo -n "."
sleep 1
done
printf "\n"
if [ $success == true ]; then
path=$target
fi
if bcachefs unlock -c $path > /dev/null 2> /dev/null; then # test for encryption
prompt $name
until bcachefs unlock $path 2> /dev/null; do # repeat until successfully unlocked
printf "unlocking failed!\n"
prompt $name
done
printf "unlocking successful.\n"
else
echo "Cannot unlock device $uuid with path $path" >&2
fi
}
'';
# we need only unlock one device manually, and cannot pass multiple at once
# remove this adaptation when bcachefs implements mounting by filesystem uuid
# also, implement automatic waiting for the constituent devices when that happens
# bcachefs does not support mounting devices with colons in the path, ergo we don't (see #49671)
firstDevice = fs: lib.head (lib.splitString ":" fs.device);
useClevis =
fs:
config.boot.initrd.clevis.enable
&& (lib.hasAttr (firstDevice fs) config.boot.initrd.clevis.devices);
openCommand =
name: fs:
if useClevis fs then
''
if clevis decrypt < /etc/clevis/${firstDevice fs}.jwe | bcachefs unlock ${firstDevice fs}
then
printf "unlocked ${name} using clevis\n"
else
printf "falling back to interactive unlocking...\n"
tryUnlock ${name} ${firstDevice fs}
fi
''
else
''
tryUnlock ${name} ${firstDevice fs}
'';
mkUnits =
prefix: name: fs:
let
parseTags =
device:
if lib.hasPrefix "LABEL=" device then
"/dev/disk/by-label/" + lib.removePrefix "LABEL=" device
else if lib.hasPrefix "UUID=" device then
"/dev/disk/by-uuid/" + lib.removePrefix "UUID=" device
else if lib.hasPrefix "PARTLABEL=" device then
"/dev/disk/by-partlabel/" + lib.removePrefix "PARTLABEL=" device
else if lib.hasPrefix "PARTUUID=" device then
"/dev/disk/by-partuuid/" + lib.removePrefix "PARTUUID=" device
else if lib.hasPrefix "ID=" device then
"/dev/disk/by-id/" + lib.removePrefix "ID=" device
else
device;
device = parseTags (firstDevice fs);
mkDeviceUnit = device: "${utils.escapeSystemdPath device}.device";
mkMountUnit = path: "${utils.escapeSystemdPath (lib.removeSuffix "/" path)}.mount";
deviceUnit = mkDeviceUnit device;
mountUnit = mkMountUnit (prefix + fs.mountPoint);
extractProperty =
prop: options: (map (lib.removePrefix prop) (builtins.filter (lib.hasPrefix prop) options));
normalizeUnits =
unit:
if lib.hasPrefix "/dev/" unit then
mkDeviceUnit unit
else if lib.hasPrefix "/" unit then
mkMountUnit unit
else
unit;
requiredUnits = map normalizeUnits (extractProperty "x-systemd.requires=" fs.options);
wantedUnits = map normalizeUnits (extractProperty "x-systemd.wants=" fs.options);
requiredMounts = extractProperty "x-systemd.requires-mounts-for=" fs.options;
wantedMounts = extractProperty "x-systemd.wants-mounts-for=" fs.options;
in
{
name = "unlock-bcachefs-${utils.escapeSystemdPath fs.mountPoint}";
value = {
description = "Unlock bcachefs for ${fs.mountPoint}";
requiredBy = [ mountUnit ];
after = [ deviceUnit ] ++ requiredUnits ++ wantedUnits;
before = [
mountUnit
"shutdown.target"
];
bindsTo = [ deviceUnit ];
requires = requiredUnits;
wants = wantedUnits;
unitConfig = {
RequiresMountsFor = requiredMounts;
WantsMountsFor = wantedMounts;
};
conflicts = [ "shutdown.target" ];
unitConfig.DefaultDependencies = false;
serviceConfig = {
Type = "oneshot";
ExecCondition = "${cfg.package}/bin/bcachefs unlock -c \"${device}\"";
Restart = "on-failure";
RestartMode = "direct";
# Ideally, this service would lock the key on stop.
# As is, RemainAfterExit doesn't accomplish anything.
RemainAfterExit = true;
};
script =
let
unlock = ''${cfg.package}/bin/bcachefs unlock "${device}"'';
unlockInteractively = ''${config.boot.initrd.systemd.package}/bin/systemd-ask-password --timeout=0 "enter passphrase for ${name}" | exec ${unlock}'';
in
if useClevis fs then
''
if ${config.boot.initrd.clevis.package}/bin/clevis decrypt < "/etc/clevis/${device}.jwe" | ${unlock}
then
printf "unlocked ${name} using clevis\n"
else
printf "falling back to interactive unlocking...\n"
${unlockInteractively}
fi
''
else
''
${unlockInteractively}
'';
};
};
in
{
options.boot.bcachefs = {
package = lib.mkPackageOption pkgs "bcachefs-tools" {
extraDescription = ''
This package should also provide a passthru 'kernelModule'
attribute to build the out-of-tree kernel module.
'';
};
modulePackage = lib.mkOption {
type = lib.types.package;
# See NOTE in linux-kernels.nix
default = config.boot.kernelPackages.callPackage cfg.package.kernelModule { };
internal = true;
};
};
options.services.bcachefs.autoScrub = {
enable = lib.mkEnableOption "regular bcachefs scrub";
fileSystems = lib.mkOption {
type = lib.types.listOf lib.types.path;
example = [ "/" ];
description = ''
List of paths to bcachefs filesystems to regularly call {command}`bcachefs scrub` on.
Defaults to all mount points with bcachefs filesystems.
'';
};
interval = lib.mkOption {
default = "monthly";
type = lib.types.str;
example = "weekly";
description = ''
Systemd calendar expression for when to scrub bcachefs filesystems.
The recommended period is a month but could be less.
See
{manpage}`systemd.time(7)`
for more information on the syntax.
'';
};
};
config = lib.mkIf (config.boot.supportedFilesystems.bcachefs or false) (
lib.mkMerge [
{
assertions = [
{
assertion =
let
kernel = config.boot.kernelPackages.kernel;
in
(
kernel.kernelAtLeast "6.7"
|| (lib.elem (kernel.structuredExtraConfig.BCACHEFS_FS or null) [
lib.kernel.module
lib.kernel.yes
(lib.kernel.option lib.kernel.yes)
])
);
message = "Linux 6.7-rc1 at minimum or a custom linux kernel with bcachefs support is required";
}
];
warnings = lib.mkIf cfg.modulePackage.meta.broken [
''
Using unmaintained in-tree bcachefs kernel module. This
will be removed in 26.05. Please use a kernel supported
by the out-of-tree module package.
''
];
# Bcachefs upstream recommends using the latest kernel
boot.kernelPackages = lib.mkDefault pkgs.linuxPackages_latest;
# needed for systemd-remount-fs
system.fsPackages = [ cfg.package ];
services.udev.packages = [ cfg.package ];
boot.extraModulePackages = lib.optionals (!cfg.modulePackage.meta.broken) [
cfg.modulePackage
];
systemd = {
packages = [ cfg.package ];
services = lib.mapAttrs' (mkUnits "") (
lib.filterAttrs (n: fs: (fs.fsType == "bcachefs") && (!utils.fsNeededForBoot fs)) config.fileSystems
);
};
}
(lib.mkIf ((config.boot.initrd.supportedFilesystems.bcachefs or false) || (bootFs != { })) {
boot.initrd.availableKernelModules = [
"bcachefs"
"sha256"
]
++ lib.optionals (config.boot.kernelPackages.kernel.kernelOlder "6.15") [
# chacha20 and poly1305 are required only for decryption attempts
# kernel 6.15 uses kernel api libraries for poly1305/chacha20: 4bf4b5046de0ef7f9dc50f3a9ef8a6dcda178a6d
# kernel 6.16 removes poly1305: ceef731b0e22df80a13d67773ae9afd55a971f9e
"poly1305"
"chacha20"
];
boot.initrd.systemd.extraBin = {
# do we need this? boot/systemd.nix:566 & boot/systemd/initrd.nix:357
"bcachefs" = "${cfg.package}/bin/bcachefs";
"mount.bcachefs" = "${cfg.package}/bin/mount.bcachefs";
};
boot.initrd.extraUtilsCommands = lib.mkIf (!config.boot.initrd.systemd.enable) ''
copy_bin_and_libs ${cfg.package}/bin/bcachefs
copy_bin_and_libs ${cfg.package}/bin/mount.bcachefs
'';
boot.initrd.extraUtilsCommandsTest = lib.mkIf (!config.boot.initrd.systemd.enable) ''
$out/bin/bcachefs version
'';
boot.initrd.postDeviceCommands = lib.mkIf (!config.boot.initrd.systemd.enable) (
commonFunctions + lib.concatStrings (lib.mapAttrsToList openCommand bootFs)
);
boot.initrd.systemd.services = lib.mapAttrs' (mkUnits "/sysroot") bootFs;
})
(lib.mkIf (cfgScrub.enable) {
assertions = [
{
assertion = lib.versionAtLeast config.boot.kernelPackages.kernel.version "6.14";
message = "Bcachefs scrubbing is supported from kernel version 6.14 or later.";
}
{
assertion = cfgScrub.enable -> (cfgScrub.fileSystems != [ ]);
message = ''
If 'services.bcachefs.autoScrub' is enabled, you need to have at least one
bcachefs file system mounted via 'fileSystems' or specify a list manually
in 'services.bcachefs.autoScrub.fileSystems'.
'';
}
];
# This will remove duplicated units from either having a filesystem mounted multiple
# time, or additionally mounted subvolumes, as well as having a filesystem span
# multiple devices (provided the same device is used to mount said filesystem).
services.bcachefs.autoScrub.fileSystems =
let
isDeviceInList = list: device: builtins.filter (e: e.device == device) list != [ ];
uniqueDeviceList = lib.foldl' (
acc: e: if isDeviceInList acc e.device then acc else acc ++ [ e ]
) [ ];
in
lib.mkDefault (
map (e: e.mountPoint) (
uniqueDeviceList (
lib.mapAttrsToList (name: fs: {
mountPoint = fs.mountPoint;
device = fs.device;
}) (lib.filterAttrs (name: fs: fs.fsType == "bcachefs") config.fileSystems)
)
)
);
systemd.timers =
let
scrubTimer =
fs:
let
fs' = if fs == "/" then "root" else utils.escapeSystemdPath fs;
in
lib.nameValuePair "bcachefs-scrub-${fs'}" {
description = "regular bcachefs scrub timer on ${fs}";
wantedBy = [ "timers.target" ];
timerConfig = {
OnCalendar = cfgScrub.interval;
AccuracySec = "1d";
Persistent = true;
};
};
in
lib.listToAttrs (map scrubTimer cfgScrub.fileSystems);
systemd.services =
let
scrubService =
fs:
let
fs' = if fs == "/" then "root" else utils.escapeSystemdPath fs;
in
lib.nameValuePair "bcachefs-scrub-${fs'}" {
description = "bcachefs scrub on ${fs}";
# scrub prevents suspend2ram or proper shutdown
conflicts = [
"shutdown.target"
"sleep.target"
];
before = [
"shutdown.target"
"sleep.target"
];
script = "${lib.getExe cfg.package} data scrub ${fs}";
serviceConfig = {
Type = "oneshot";
Nice = 19;
IOSchedulingClass = "idle";
};
};
in
lib.listToAttrs (map scrubService cfgScrub.fileSystems);
})
]
);
meta = {
inherit (pkgs.bcachefs-tools.meta) maintainers;
};
}

View File

@@ -0,0 +1,15 @@
{
config,
lib,
pkgs,
...
}:
{
config = lib.mkIf (config.boot.supportedFilesystems."fuse.bindfs" or false) {
system.fsPackages = [ pkgs.bindfs ];
};
meta = {
maintainers = with lib.maintainers; [ Luflosi ];
};
}

View File

@@ -0,0 +1,199 @@
{
config,
lib,
pkgs,
utils,
...
}:
let
inherit (lib)
mkEnableOption
mkOption
types
mkMerge
mkIf
optionals
mkDefault
nameValuePair
listToAttrs
filterAttrs
mapAttrsToList
foldl'
;
inInitrd = config.boot.initrd.supportedFilesystems.btrfs or false;
inSystem = config.boot.supportedFilesystems.btrfs or false;
cfgScrub = config.services.btrfs.autoScrub;
enableAutoScrub = cfgScrub.enable;
enableBtrfs = inInitrd || inSystem || enableAutoScrub;
in
{
options = {
# One could also do regular btrfs balances, but that shouldn't be necessary
# during normal usage and as long as the filesystems aren't filled near capacity
services.btrfs.autoScrub = {
enable = mkEnableOption "regular btrfs scrub";
fileSystems = mkOption {
type = types.listOf types.path;
example = [ "/" ];
description = ''
List of paths to btrfs filesystems to regularly call {command}`btrfs scrub` on.
Defaults to all mount points with btrfs filesystems.
Note that if you have filesystems that span multiple devices (e.g. RAID), you should
take care to use the same device for any given mount point and let btrfs take care
of automatically mounting the rest, in order to avoid scrubbing the same data multiple times.
'';
};
interval = mkOption {
default = "monthly";
type = types.str;
example = "weekly";
description = ''
Systemd calendar expression for when to scrub btrfs filesystems.
The recommended period is a month but could be less
({manpage}`btrfs-scrub(8)`).
See
{manpage}`systemd.time(7)`
for more information on the syntax.
'';
};
};
};
config = mkMerge [
(mkIf enableBtrfs {
system.fsPackages = [ pkgs.btrfs-progs ];
})
(mkIf inInitrd {
boot.initrd.kernelModules = [ "btrfs" ];
boot.initrd.availableKernelModules = [
"crc32c"
]
++ optionals (config.boot.kernelPackages.kernel.kernelAtLeast "5.5") [
# Needed for mounting filesystems with new checksums
"xxhash_generic"
"blake2b_generic"
# `sha256` is always available, whereas `sha256_generic` is not available from 6.17 onwards
"sha256" # Should be baked into our kernel, just to be sure
];
boot.initrd.extraUtilsCommands = mkIf (!config.boot.initrd.systemd.enable) ''
copy_bin_and_libs ${pkgs.btrfs-progs}/bin/btrfs
ln -sv btrfs $out/bin/btrfsck
ln -sv btrfsck $out/bin/fsck.btrfs
'';
boot.initrd.extraUtilsCommandsTest = mkIf (!config.boot.initrd.systemd.enable) ''
$out/bin/btrfs --version
'';
boot.initrd.postDeviceCommands = mkIf (!config.boot.initrd.systemd.enable) ''
btrfs device scan
'';
boot.initrd.systemd.initrdBin = [ pkgs.btrfs-progs ];
})
(mkIf enableAutoScrub {
assertions = [
{
assertion = cfgScrub.enable -> (cfgScrub.fileSystems != [ ]);
message = ''
If 'services.btrfs.autoScrub' is enabled, you need to have at least one
btrfs file system mounted via 'fileSystems' or specify a list manually
in 'services.btrfs.autoScrub.fileSystems'.
'';
}
];
# This will remove duplicated units from either having a filesystem mounted multiple
# time, or additionally mounted subvolumes, as well as having a filesystem span
# multiple devices (provided the same device is used to mount said filesystem).
services.btrfs.autoScrub.fileSystems =
let
isDeviceInList = list: device: builtins.filter (e: e.device == device) list != [ ];
uniqueDeviceList = foldl' (acc: e: if isDeviceInList acc e.device then acc else acc ++ [ e ]) [ ];
in
mkDefault (
map (e: e.mountPoint) (
uniqueDeviceList (
mapAttrsToList (name: fs: {
mountPoint = fs.mountPoint;
device = fs.device;
}) (filterAttrs (name: fs: fs.fsType == "btrfs") config.fileSystems)
)
)
);
# TODO: Did not manage to do it via the usual btrfs-scrub@.timer/.service
# template units due to problems enabling the parameterized units,
# so settled with many units and templating via nix for now.
# https://github.com/NixOS/nixpkgs/pull/32496#discussion_r156527544
systemd.timers =
let
scrubTimer =
fs:
let
fs' = utils.escapeSystemdPath fs;
in
nameValuePair "btrfs-scrub-${fs'}" {
description = "regular btrfs scrub timer on ${fs}";
wantedBy = [ "timers.target" ];
timerConfig = {
OnCalendar = cfgScrub.interval;
AccuracySec = "1d";
Persistent = true;
};
};
in
listToAttrs (map scrubTimer cfgScrub.fileSystems);
systemd.services =
let
scrubService =
fs:
let
fs' = utils.escapeSystemdPath fs;
in
nameValuePair "btrfs-scrub-${fs'}" {
description = "btrfs scrub on ${fs}";
documentation = [ "man:btrfs-scrub(8)" ];
# scrub prevents suspend2ram or proper shutdown
conflicts = [
"shutdown.target"
"sleep.target"
];
before = [
"shutdown.target"
"sleep.target"
];
serviceConfig = {
# simple and not oneshot, otherwise ExecStop is not used
Type = "simple";
Nice = 19;
IOSchedulingClass = "idle";
ExecStart = "${pkgs.btrfs-progs}/bin/btrfs scrub start -B ${fs}";
# if the service is stopped before scrub end, cancel it
ExecStop = pkgs.writeShellScript "btrfs-scrub-maybe-cancel" ''
(${pkgs.btrfs-progs}/bin/btrfs scrub status ${fs} | ${pkgs.gnugrep}/bin/grep finished) || ${pkgs.btrfs-progs}/bin/btrfs scrub cancel ${fs}
'';
};
};
in
listToAttrs (map scrubService cfgScrub.fileSystems);
})
];
}

View File

@@ -0,0 +1,38 @@
{
config,
lib,
pkgs,
...
}:
with lib;
let
inInitrd = config.boot.initrd.supportedFilesystems.cifs or false;
in
{
config = {
system.fsPackages = mkIf (config.boot.supportedFilesystems.cifs or false) [ pkgs.cifs-utils ];
boot.initrd.availableKernelModules = mkIf inInitrd [
"cifs"
"nls_utf8"
"hmac"
"md4"
"ecb"
"des_generic"
"sha256"
];
boot.initrd.extraUtilsCommands = mkIf (inInitrd && !config.boot.initrd.systemd.enable) ''
copy_bin_and_libs ${pkgs.cifs-utils}/sbin/mount.cifs
'';
boot.initrd.systemd.extraBin."mount.cifs" = mkIf inInitrd "${pkgs.cifs-utils}/sbin/mount.cifs";
};
}

View File

@@ -0,0 +1,29 @@
{
config,
lib,
pkgs,
...
}:
# TODO: make ecryptfs work in initramfs?
with lib;
{
config = mkIf (config.boot.supportedFilesystems.ecryptfs or false) {
system.fsPackages = [ pkgs.ecryptfs ];
security.wrappers = {
"mount.ecryptfs_private" = {
setuid = true;
owner = "root";
group = "root";
source = "${pkgs.ecryptfs.out}/bin/mount.ecryptfs_private";
};
"umount.ecryptfs_private" = {
setuid = true;
owner = "root";
group = "root";
source = "${pkgs.ecryptfs.out}/bin/umount.ecryptfs_private";
};
};
};
}

View File

@@ -0,0 +1,78 @@
{
pkgs,
config,
lib,
...
}:
let
cfg = config.services.envfs;
mounts = {
"/usr/bin" = {
device = "none";
fsType = "envfs";
options = [
"bind-mount=/bin"
"fallback-path=${
pkgs.runCommand "fallback-path" { } (
''
mkdir -p $out
ln -s ${config.environment.usrbinenv} $out/env
ln -s ${config.environment.binsh} $out/sh
''
+ cfg.extraFallbackPathCommands
)
}"
"nofail"
];
};
# We need to bind-mount /bin to /usr/bin, because otherwise upgrading
# from envfs < 1.0.5 will cause having the old envs with no /bin bind mount.
# Systemd is smart enough to not mount /bin if it's already mounted.
"/bin" = {
device = "/usr/bin";
fsType = "none";
options = [
"bind"
"nofail"
];
};
};
in
{
options = {
services.envfs = {
enable = lib.mkEnableOption "Envfs filesystem" // {
description = ''
Fuse filesystem that returns symlinks to executables based on the PATH
of the requesting process. This is useful to execute shebangs on NixOS
that assume hard coded locations in locations like /bin or /usr/bin
etc.
'';
};
package = lib.mkOption {
type = lib.types.package;
default = pkgs.envfs;
defaultText = lib.literalExpression "pkgs.envfs";
description = "Which package to use for the envfs.";
};
extraFallbackPathCommands = lib.mkOption {
type = lib.types.lines;
default = "";
example = "ln -s $''{pkgs.bash}/bin/bash $out/bash";
description = "Extra commands to run in the package that contains fallback executables in case not other executable is found";
};
};
};
config = lib.mkIf (cfg.enable) {
environment.systemPackages = [ cfg.package ];
# we also want these mounts in virtual machines.
fileSystems = if config.virtualisation ? qemu then lib.mkVMOverride mounts else mounts;
# We no longer need those when using envfs
system.activationScripts.usrbinenv = lib.mkForce "";
system.activationScripts.binsh = lib.mkForce "";
};
}

View File

@@ -0,0 +1,26 @@
{
config,
lib,
pkgs,
...
}:
let
inInitrd = config.boot.initrd.supportedFilesystems.erofs or false;
inSystem = config.boot.supportedFilesystems.erofs or false;
in
{
config = lib.mkIf (inInitrd || inSystem) {
system.fsPackages = [ pkgs.erofs-utils ];
boot.initrd.availableKernelModules = lib.mkIf inInitrd [ "erofs" ];
# fsck.erofs is currently experimental and should not be run as a
# privileged user. Thus, it is not included in the initrd.
};
}

View File

@@ -0,0 +1,22 @@
{
config,
lib,
pkgs,
...
}:
with lib;
{
config = mkIf (config.boot.supportedFilesystems.exfat or false) {
system.fsPackages =
if config.boot.kernelPackages.kernelOlder "5.7" then
[
pkgs.exfat # FUSE
]
else
[
pkgs.exfatprogs # non-FUSE
];
};
}

View File

@@ -0,0 +1,42 @@
{
config,
lib,
pkgs,
...
}:
let
hasExtX = s: s.ext2 or s.ext3 or s.ext4 or false;
inInitrd = hasExtX config.boot.initrd.supportedFilesystems;
inSystem = hasExtX config.boot.supportedFilesystems;
in
{
config = {
system.fsPackages = lib.mkIf (config.boot.initrd.systemd.enable -> (inInitrd || inSystem)) [
pkgs.e2fsprogs
];
# As of kernel 4.3, there is no separate ext3 driver (they're also handled by ext4.ko)
boot.initrd.availableKernelModules = lib.mkIf (config.boot.initrd.systemd.enable -> inInitrd) [
"ext2"
"ext4"
];
boot.initrd.extraUtilsCommands = lib.mkIf (!config.boot.initrd.systemd.enable) ''
# Copy e2fsck and friends.
copy_bin_and_libs ${pkgs.e2fsprogs}/sbin/e2fsck
copy_bin_and_libs ${pkgs.e2fsprogs}/sbin/tune2fs
ln -sv e2fsck $out/bin/fsck.ext2
ln -sv e2fsck $out/bin/fsck.ext3
ln -sv e2fsck $out/bin/fsck.ext4
'';
boot.initrd.systemd.initrdBin = lib.mkIf inInitrd [ pkgs.e2fsprogs ];
};
}

View File

@@ -0,0 +1,29 @@
{
config,
pkgs,
lib,
...
}:
with lib;
let
inInitrd = config.boot.initrd.supportedFilesystems.f2fs or false;
in
{
config = mkIf (config.boot.supportedFilesystems.f2fs or false) {
system.fsPackages = [ pkgs.f2fs-tools ];
boot.initrd.availableKernelModules = mkIf inInitrd [
"f2fs"
"crc32"
];
boot.initrd.extraUtilsCommands = mkIf (inInitrd && !config.boot.initrd.systemd.enable) ''
copy_bin_and_libs ${pkgs.f2fs-tools}/sbin/fsck.f2fs
'';
boot.initrd.systemd.initrdBin = mkIf inInitrd [ pkgs.f2fs-tools ];
};
}

View File

@@ -0,0 +1,16 @@
{
config,
lib,
pkgs,
...
}:
with lib;
{
config = mkIf (config.boot.supportedFilesystems.glusterfs or false) {
system.fsPackages = [ pkgs.glusterfs ];
};
}

View File

@@ -0,0 +1,26 @@
{
config,
lib,
pkgs,
...
}:
with lib;
let
inInitrd = config.boot.initrd.supportedFilesystems.jfs or false;
in
{
config = mkIf (config.boot.supportedFilesystems.jfs or false) {
system.fsPackages = [ pkgs.jfsutils ];
boot.initrd.kernelModules = mkIf inInitrd [ "jfs" ];
boot.initrd.extraUtilsCommands = mkIf (inInitrd && !config.boot.initrd.systemd.enable) ''
copy_bin_and_libs ${pkgs.jfsutils}/sbin/fsck.jfs
'';
boot.initrd.systemd.initrdBin = mkIf inInitrd [ pkgs.jfsutils ];
};
}

View File

@@ -0,0 +1,217 @@
{
config,
lib,
pkgs,
...
}:
let
inInitrd = config.boot.initrd.supportedFilesystems.nfs or false;
nfsStateDir = "/var/lib/nfs";
rpcMountpoint = "${nfsStateDir}/rpc_pipefs";
format = pkgs.formats.ini { };
idmapdConfFile = format.generate "idmapd.conf" cfg.idmapd.settings;
# merge parameters from services.nfs.server
nfsConfSettings =
lib.optionalAttrs (cfg.server.nproc != null) {
nfsd.threads = cfg.server.nproc;
}
// lib.optionalAttrs (cfg.server.hostName != null) {
nfsd.host = cfg.server.hostName;
}
// lib.optionalAttrs (cfg.server.mountdPort != null) {
mountd.port = cfg.server.mountdPort;
}
// lib.optionalAttrs (cfg.server.statdPort != null) {
statd.port = cfg.server.statdPort;
}
// lib.optionalAttrs (cfg.server.lockdPort != null) {
lockd.port = cfg.server.lockdPort;
lockd.udp-port = cfg.server.lockdPort;
};
nfsConfDeprecated = cfg.extraConfig + ''
[nfsd]
threads=${toString cfg.server.nproc}
${lib.optionalString (cfg.server.hostName != null) "host=${cfg.server.hostName}"}
${cfg.server.extraNfsdConfig}
[mountd]
${lib.optionalString (cfg.server.mountdPort != null) "port=${toString cfg.server.mountdPort}"}
[statd]
${lib.optionalString (cfg.server.statdPort != null) "port=${toString cfg.server.statdPort}"}
[lockd]
${lib.optionalString (cfg.server.lockdPort != null) ''
port=${toString cfg.server.lockdPort}
udp-port=${toString cfg.server.lockdPort}
''}
'';
nfsConfFile =
if cfg.settings != { } then
format.generate "nfs.conf" (lib.recursiveUpdate nfsConfSettings cfg.settings)
else
pkgs.writeText "nfs.conf" nfsConfDeprecated;
requestKeyConfFile = pkgs.writeText "request-key.conf" ''
create id_resolver * * ${pkgs.nfs-utils}/bin/nfsidmap -t 600 %k %d
'';
cfg = config.services.nfs;
in
{
###### interface
options = {
services.nfs = {
idmapd.settings = lib.mkOption {
type = format.type;
default = { };
description = ''
libnfsidmap configuration. Refer to
<https://linux.die.net/man/5/idmapd.conf>
for details.
'';
example = lib.literalExpression ''
{
Translation = {
GSS-Methods = "static,nsswitch";
};
Static = {
"root/hostname.domain.com@REALM.COM" = "root";
};
}
'';
};
settings = lib.mkOption {
type = format.type;
default = { };
description = ''
General configuration for NFS daemons and tools.
See {manpage}`nfs.conf(5)` and related man pages for details.
'';
example = lib.literalExpression ''
{
mountd.manage-gids = true;
}
'';
};
extraConfig = lib.mkOption {
type = lib.types.lines;
default = "";
description = ''
Extra nfs-utils configuration.
'';
};
};
};
###### implementation
config =
lib.mkIf (config.boot.supportedFilesystems.nfs or config.boot.supportedFilesystems.nfs4 or false)
{
warnings =
(lib.optional (cfg.extraConfig != "") ''
`services.nfs.extraConfig` is deprecated. Use `services.nfs.settings` instead.
'')
++ (lib.optional (cfg.server.extraNfsdConfig != "") ''
`services.nfs.server.extraNfsdConfig` is deprecated. Use `services.nfs.settings` instead.
'');
assertions = [
{
assertion = cfg.settings != { } -> cfg.extraConfig == "" && cfg.server.extraNfsdConfig == "";
message = "`services.nfs.settings` cannot be used together with `services.nfs.extraConfig` and `services.nfs.server.extraNfsdConfig`.";
}
];
services.rpcbind.enable = true;
services.nfs.idmapd.settings = {
General = lib.mkMerge [
{ Pipefs-Directory = rpcMountpoint; }
(lib.mkIf (config.networking.domain != null) { Domain = config.networking.domain; })
];
Mapping = {
Nobody-User = "nobody";
Nobody-Group = "nogroup";
};
Translation = {
Method = "nsswitch";
};
};
system.fsPackages = [ pkgs.nfs-utils ];
boot.initrd.kernelModules = lib.mkIf inInitrd [ "nfs" ];
systemd.packages = [ pkgs.nfs-utils ];
environment.systemPackages = [ pkgs.keyutils ];
environment.etc = {
"idmapd.conf".source = idmapdConfFile;
"nfs.conf".source = nfsConfFile;
"request-key.conf".source = requestKeyConfFile;
};
systemd.services.nfs-blkmap = {
restartTriggers = [ nfsConfFile ];
};
systemd.targets.nfs-client = {
wantedBy = [
"multi-user.target"
"remote-fs.target"
];
};
systemd.services.nfs-idmapd = {
restartTriggers = [ idmapdConfFile ];
};
systemd.services.nfs-mountd = {
restartTriggers = [ nfsConfFile ];
enable = lib.mkDefault false;
};
systemd.services.nfs-server = {
restartTriggers = [ nfsConfFile ];
enable = lib.mkDefault false;
};
systemd.services.auth-rpcgss-module = {
unitConfig.ConditionPathExists = [
""
"/etc/krb5.keytab"
];
};
systemd.services.rpc-gssd = {
restartTriggers = [ nfsConfFile ];
unitConfig.ConditionPathExists = [
""
"/etc/krb5.keytab"
];
};
systemd.services.rpc-statd = {
restartTriggers = [ nfsConfFile ];
preStart = ''
mkdir -p /var/lib/nfs/{sm,sm.bak}
'';
};
};
}

View File

@@ -0,0 +1,18 @@
{
config,
lib,
pkgs,
...
}:
with lib;
{
config =
mkIf (config.boot.supportedFilesystems.ntfs or config.boot.supportedFilesystems.ntfs-3g or false)
{
system.fsPackages = [ pkgs.ntfs3g ];
};
}

View File

@@ -0,0 +1,200 @@
{
config,
lib,
pkgs,
utils,
...
}:
let
# The scripted initrd contains some magic to add the prefix to the
# paths just in time, so we don't add it here.
sysrootPrefix =
fs:
if
config.boot.initrd.systemd.enable
&& fs.overlay.useStage1BaseDirectories
&& (utils.fsNeededForBoot fs)
then
"/sysroot"
else
"";
# Returns a service that creates the required directories before the mount is
# created.
preMountService =
_name: fs:
let
prefix = sysrootPrefix fs;
escapedMountpoint = utils.escapeSystemdPath (prefix + fs.mountPoint);
mountUnit = "${escapedMountpoint}.mount";
upperdir = prefix + fs.overlay.upperdir;
workdir = prefix + fs.overlay.workdir;
in
lib.mkIf (fs.overlay.upperdir != null) {
"rw-${escapedMountpoint}" = {
requiredBy = [ mountUnit ];
before = [ mountUnit ];
unitConfig = {
DefaultDependencies = false;
RequiresMountsFor = "${upperdir} ${workdir}";
};
serviceConfig = {
Type = "oneshot";
ExecStart = "${pkgs.coreutils}/bin/mkdir -p -m 0755 ${upperdir} ${workdir}";
};
};
};
overlayOpts =
{
config,
...
}:
{
options.overlay = {
lowerdir = lib.mkOption {
type = with lib.types; nullOr (nonEmptyListOf (either str pathInStore));
default = null;
description = ''
The list of path(s) to the lowerdir(s).
To create a writable overlay, you MUST provide an `upperdir` and a
`workdir`.
You can create a read-only overlay when you provide multiple (at
least 2!) lowerdirs and neither an `upperdir` nor a `workdir`.
'';
};
upperdir = lib.mkOption {
type = lib.types.nullOr lib.types.str;
default = null;
description = ''
The path to the upperdir.
If this is null, a read-only overlay is created using the lowerdir.
If the filesystem is `neededForBoot`, this will be prefixed with `/sysroot`,
unless `useStage1BaseDirectories` is set to `true`.
If you set this to some value you MUST also set `workdir`.
'';
};
workdir = lib.mkOption {
type = lib.types.nullOr lib.types.str;
default = null;
description = ''
The path to the workdir.
If the filesystem is `neededForBoot`, this will be prefixed with `/sysroot`,
unless `useStage1BaseDirectories` is set to `true`.
This MUST be set if you set `upperdir`.
'';
};
useStage1BaseDirectories = lib.mkOption {
type = lib.types.bool;
default = true;
description = ''
If enabled, `lowerdir`, `upperdir` and `workdir` will be prefixed with `/sysroot`.
Disabling this can be useful to create an overlay over directories which aren't on the real root.
Disabling this does not work with the scripted (i.e. non-systemd) initrd.
'';
};
};
config = lib.mkIf (config.overlay.lowerdir != null) {
fsType = "overlay";
device = lib.mkDefault "overlay";
depends = map (x: "${x}") (
config.overlay.lowerdir
++ lib.optionals (config.overlay.upperdir != null) [
config.overlay.upperdir
config.overlay.workdir
]
);
options =
let
prefix = sysrootPrefix config;
lowerdir = map (s: prefix + s) config.overlay.lowerdir;
upperdir = prefix + config.overlay.upperdir;
workdir = prefix + config.overlay.workdir;
in
[
"lowerdir=${lib.concatStringsSep ":" lowerdir}"
]
++ lib.optionals (config.overlay.upperdir != null) [
"upperdir=${upperdir}"
"workdir=${workdir}"
];
};
};
in
{
options = {
# Merge the overlay options into the fileSystems option.
fileSystems = lib.mkOption {
type = lib.types.attrsOf (lib.types.submodule [ overlayOpts ]);
};
};
config =
let
overlayFileSystems = lib.filterAttrs (_name: fs: (fs.overlay.lowerdir != null)) config.fileSystems;
initrdFileSystems = lib.filterAttrs (_name: utils.fsNeededForBoot) overlayFileSystems;
userspaceFileSystems = lib.filterAttrs (_name: fs: (!utils.fsNeededForBoot fs)) overlayFileSystems;
in
{
boot.initrd.availableKernelModules = lib.mkIf (initrdFileSystems != { }) [ "overlay" ];
assertions =
lib.concatLists (
lib.mapAttrsToList (_name: fs: [
{
assertion = (fs.overlay.upperdir == null) == (fs.overlay.workdir == null);
message = "You cannot define a `lowerdir` without a `workdir` and vice versa for mount point: ${fs.mountPoint}";
}
{
assertion =
(fs.overlay.lowerdir != null && fs.overlay.upperdir == null)
-> (lib.length fs.overlay.lowerdir) >= 2;
message = "A read-only overlay (without an `upperdir`) requires at least 2 `lowerdir`s: ${fs.mountPoint}";
}
{
assertion = !fs.overlay.useStage1BaseDirectories -> config.boot.initrd.systemd.enable;
message = ''
Stage 1 overlay file system ${fs.mountPoint} has 'useStage1BaseDirectories' set to false,
which is not supported with scripted initrd. Please enable 'boot.initrd.systemd.enable'.
'';
}
]) overlayFileSystems
)
++ lib.mapAttrsToList (_: fs: {
assertion = fs.overlay.upperdir == null -> config.boot.initrd.systemd.enable;
message = ''
Stage 1 overlay file system ${fs.mountPoint} has no upperdir,
which is not supported with scripted initrd. Please enable
'boot.initrd.systemd.enable'.
'';
}) initrdFileSystems;
boot.initrd.systemd.services = lib.mkMerge (lib.mapAttrsToList preMountService initrdFileSystems);
systemd.services = lib.mkMerge (lib.mapAttrsToList preMountService userspaceFileSystems);
};
}

View File

@@ -0,0 +1,31 @@
{
config,
lib,
pkgs,
...
}:
with lib;
let
inInitrd = config.boot.initrd.supportedFilesystems.reiserfs or false;
in
{
config = mkIf (config.boot.supportedFilesystems.reiserfs or false) {
system.fsPackages = [ pkgs.reiserfsprogs ];
boot.initrd.kernelModules = mkIf inInitrd [ "reiserfs" ];
boot.initrd.extraUtilsCommands = mkIf (inInitrd && !config.boot.initrd.systemd.enable) ''
copy_bin_and_libs ${pkgs.reiserfsprogs}/sbin/reiserfsck
ln -s reiserfsck $out/bin/fsck.reiserfs
'';
boot.initrd.systemd.initrdBin = mkIf inInitrd [ pkgs.reiserfsprogs ];
};
}

View File

@@ -0,0 +1,13 @@
{ config, lib, ... }:
let
inInitrd = config.boot.initrd.supportedFilesystems.squashfs or false;
in
{
boot.initrd.availableKernelModules = lib.mkIf inInitrd [ "squashfs" ];
}

View File

@@ -0,0 +1,15 @@
{
config,
lib,
pkgs,
...
}:
{
config =
lib.mkIf
(config.boot.supportedFilesystems.sshfs or config.boot.supportedFilesystems."fuse.sshfs" or false)
{
system.fsPackages = [ pkgs.sshfs ];
};
}

View File

@@ -0,0 +1,48 @@
{
config,
pkgs,
lib,
...
}:
{
config = lib.mkMerge [
(lib.mkIf (config.boot.initrd.supportedFilesystems.unionfs-fuse or false) {
boot.initrd.kernelModules = [ "fuse" ];
boot.initrd.extraUtilsCommands = lib.mkIf (!config.boot.initrd.systemd.enable) ''
copy_bin_and_libs ${pkgs.fuse}/sbin/mount.fuse
copy_bin_and_libs ${pkgs.unionfs-fuse}/bin/unionfs
substitute ${pkgs.unionfs-fuse}/sbin/mount.unionfs-fuse $out/bin/mount.unionfs-fuse \
--replace '${pkgs.bash}/bin/bash' /bin/sh \
--replace '${pkgs.fuse}/sbin' /bin \
--replace '${pkgs.unionfs-fuse}/bin' /bin
chmod +x $out/bin/mount.unionfs-fuse
'';
boot.initrd.postDeviceCommands = lib.mkIf (!config.boot.initrd.systemd.enable) ''
# Hacky!!! fuse hard-codes the path to mount
mkdir -p /nix/store/eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee-${pkgs.util-linux.name}-bin/bin
ln -s $(which mount) /nix/store/eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee-${pkgs.util-linux.name}-bin/bin
ln -s $(which umount) /nix/store/eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee-${pkgs.util-linux.name}-bin/bin
'';
boot.initrd.systemd.extraBin = {
"mount.fuse" = "${pkgs.fuse}/bin/mount.fuse";
"unionfs" = "${pkgs.unionfs-fuse}/bin/unionfs";
"mount.unionfs-fuse" = pkgs.runCommand "mount.unionfs-fuse" { } ''
substitute ${pkgs.unionfs-fuse}/sbin/mount.unionfs-fuse $out \
--replace '${pkgs.bash}/bin/bash' /bin/sh \
--replace '${pkgs.fuse}/sbin' /bin \
--replace '${pkgs.unionfs-fuse}/bin' /bin
'';
};
})
(lib.mkIf (config.boot.supportedFilesystems.unionfs-fuse or false) {
system.fsPackages = [ pkgs.unionfs-fuse ];
})
];
}

View File

@@ -0,0 +1,28 @@
{
config,
lib,
pkgs,
...
}:
with lib;
let
inInitrd = config.boot.initrd.supportedFilesystems.vboxsf or false;
package = pkgs.runCommand "mount.vboxsf" { preferLocalBuild = true; } ''
mkdir -p $out/bin
cp ${pkgs.linuxPackages.virtualboxGuestAdditions}/bin/mount.vboxsf $out/bin
'';
in
{
config = mkIf (config.boot.supportedFilesystems.vboxsf or false) {
system.fsPackages = [ package ];
boot.initrd.kernelModules = mkIf inInitrd [ "vboxsf" ];
};
}

View File

@@ -0,0 +1,38 @@
{
config,
lib,
pkgs,
...
}:
with lib;
let
inInitrd = config.boot.initrd.supportedFilesystems.vfat or false;
in
{
config = mkIf (config.boot.supportedFilesystems.vfat or false) {
system.fsPackages = [
pkgs.dosfstools
pkgs.mtools
];
boot.initrd.kernelModules = mkIf inInitrd [
"vfat"
"nls_cp437"
"nls_iso8859-1"
];
boot.initrd.extraUtilsCommands = mkIf (inInitrd && !config.boot.initrd.systemd.enable) ''
copy_bin_and_libs ${pkgs.dosfstools}/sbin/dosfsck
ln -sv dosfsck $out/bin/fsck.vfat
'';
boot.initrd.systemd.initrdBin = mkIf inInitrd [ pkgs.dosfstools ];
};
}

View File

@@ -0,0 +1,38 @@
{
config,
lib,
pkgs,
...
}:
with lib;
let
inInitrd = config.boot.initrd.supportedFilesystems.xfs or false;
in
{
config = mkIf (config.boot.supportedFilesystems.xfs or false) {
system.fsPackages = [ pkgs.xfsprogs.bin ];
boot.initrd.availableKernelModules = mkIf inInitrd [
"xfs"
"crc32c"
];
boot.initrd.extraUtilsCommands = mkIf (inInitrd && !config.boot.initrd.systemd.enable) ''
copy_bin_and_libs ${pkgs.xfsprogs.bin}/bin/fsck.xfs
copy_bin_and_libs ${pkgs.xfsprogs.bin}/bin/xfs_repair
'';
# Trick just to set 'sh' after the extraUtils nuke-refs.
boot.initrd.extraUtilsCommandsTest = mkIf (inInitrd && !config.boot.initrd.systemd.enable) ''
sed -i -e 's,^#!.*,#!'$out/bin/sh, $out/bin/fsck.xfs
'';
boot.initrd.systemd.initrdBin = mkIf inInitrd [ pkgs.xfsprogs.bin ];
};
}

File diff suppressed because it is too large Load Diff

191
nixos/modules/tasks/lvm.nix Normal file
View File

@@ -0,0 +1,191 @@
{
config,
lib,
pkgs,
...
}:
with lib;
let
cfg = config.services.lvm;
in
{
options.services.lvm = {
enable = mkEnableOption "lvm2" // {
default = true;
description = ''
Whether to enable lvm2.
:::{.note}
The lvm2 package contains device-mapper udev rules and without those tools like cryptsetup do not fully function!
:::
'';
};
package = mkOption {
type = types.package;
default = pkgs.lvm2;
internal = true;
defaultText = literalExpression "pkgs.lvm2";
description = ''
This option allows you to override the LVM package that's used on the system
(udev rules, tmpfiles, systemd services).
Defaults to pkgs.lvm2, pkgs.lvm2_dmeventd if dmeventd or pkgs.lvm2_vdo if vdo is enabled.
'';
};
dmeventd.enable = mkEnableOption "the LVM dmevent daemon";
boot.thin.enable = mkEnableOption "support for booting from ThinLVs";
boot.vdo.enable = mkEnableOption "support for booting from VDOLVs";
};
options.boot.initrd.services.lvm.enable = mkEnableOption "booting from LVM2 in the initrd" // {
description = ''
*This will only be used when systemd is used in stage 1.*
Whether to enable booting from LVM2 in the initrd.
'';
default = config.boot.initrd.systemd.enable && config.services.lvm.enable;
defaultText = lib.literalExpression "config.boot.initrd.systemd.enable && config.services.lvm.enable";
};
config = mkMerge [
{
# minimal configuration file to make lvmconfig/lvm2-activation-generator happy
environment.etc."lvm/lvm.conf".text = "config {}";
}
(mkIf cfg.enable {
systemd.tmpfiles.packages = [ cfg.package.out ];
environment.systemPackages = [ cfg.package ];
systemd.packages = [ cfg.package ];
services.udev.packages = [ cfg.package.out ];
})
(mkIf config.boot.initrd.services.lvm.enable {
# We need lvm2 for the device-mapper rules
boot.initrd.services.udev.packages = [ cfg.package ];
# The device-mapper rules want to call tools from lvm2
boot.initrd.systemd.initrdBin = [ cfg.package ];
boot.initrd.services.udev.binPackages = [ cfg.package ];
})
(mkIf cfg.dmeventd.enable {
systemd.sockets."dm-event".wantedBy = [ "sockets.target" ];
systemd.services."lvm2-monitor".wantedBy = [ "sysinit.target" ];
environment.etc."lvm/lvm.conf".text = ''
dmeventd/executable = "${cfg.package}/bin/dmeventd"
'';
services.lvm.package = mkDefault pkgs.lvm2_dmeventd;
})
(mkIf cfg.boot.thin.enable {
boot.initrd = {
kernelModules = [
"dm-snapshot"
"dm-thin-pool"
];
systemd.initrdBin = lib.mkIf config.boot.initrd.services.lvm.enable [
pkgs.thin-provisioning-tools
];
extraUtilsCommands = mkIf (!config.boot.initrd.systemd.enable) ''
for BIN in ${pkgs.thin-provisioning-tools}/bin/*; do
copy_bin_and_libs $BIN
done
'';
extraUtilsCommandsTest = mkIf (!config.boot.initrd.systemd.enable) ''
ls ${pkgs.thin-provisioning-tools}/bin/ | grep -v pdata_tools | while read BIN; do
$out/bin/$(basename $BIN) --help > /dev/null
done
'';
};
environment.etc."lvm/lvm.conf".text =
concatMapStringsSep "\n"
(bin: "global/${bin}_executable = ${pkgs.thin-provisioning-tools}/bin/${bin}")
[
"thin_check"
"thin_dump"
"thin_repair"
"cache_check"
"cache_dump"
"cache_repair"
];
environment.systemPackages = [ pkgs.thin-provisioning-tools ];
})
(mkIf cfg.boot.vdo.enable {
assertions = [
{
assertion = lib.versionAtLeast config.boot.kernelPackages.kernel.version "6.9";
message = "boot.vdo.enable requires at least kernel version 6.9";
}
];
boot = {
initrd = {
kernelModules = [ "dm-vdo" ];
systemd.initrdBin = lib.mkIf config.boot.initrd.services.lvm.enable [ pkgs.vdo ];
extraUtilsCommands = mkIf (!config.boot.initrd.systemd.enable) ''
ls ${pkgs.vdo}/bin/ | while read BIN; do
copy_bin_and_libs ${pkgs.vdo}/bin/$BIN
done
substituteInPlace $out/bin/vdorecover --replace "${pkgs.bash}/bin/bash" "/bin/sh"
substituteInPlace $out/bin/adaptlvm --replace "${pkgs.bash}/bin/bash" "/bin/sh"
'';
extraUtilsCommandsTest = mkIf (!config.boot.initrd.systemd.enable) ''
ls ${pkgs.vdo}/bin/ | grep -vE '(adaptlvm|vdorecover)' | while read BIN; do
$out/bin/$(basename $BIN) --help > /dev/null
done
'';
};
};
services.lvm.package = mkOverride 999 pkgs.lvm2_vdo; # this overrides mkDefault
environment.systemPackages = [ pkgs.vdo ];
})
(mkIf (cfg.dmeventd.enable || cfg.boot.thin.enable) {
boot.initrd.systemd.contents."/etc/lvm/lvm.conf".text =
optionalString (config.boot.initrd.services.lvm.enable && cfg.boot.thin.enable) (
concatMapStringsSep "\n" (bin: "global/${bin}_executable = /bin/${bin}") [
"thin_check"
"thin_dump"
"thin_repair"
"cache_check"
"cache_dump"
"cache_repair"
]
)
+ "\n"
+ optionalString cfg.dmeventd.enable ''
dmeventd/executable = /bin/false
activation/monitoring = 0
'';
boot.initrd.preLVMCommands = mkIf (!config.boot.initrd.systemd.enable) ''
mkdir -p /etc/lvm
cat << EOF >> /etc/lvm/lvm.conf
${optionalString cfg.boot.thin.enable (
concatMapStringsSep "\n" (bin: "global/${bin}_executable = $(command -v ${bin})") [
"thin_check"
"thin_dump"
"thin_repair"
"cache_check"
"cache_dump"
"cache_repair"
]
)}
${optionalString cfg.dmeventd.enable ''
dmeventd/executable = "$(command -v false)"
activation/monitoring = 0
''}
EOF
'';
})
];
}

View File

@@ -0,0 +1,834 @@
{
config,
lib,
pkgs,
utils,
...
}:
with utils;
with lib;
let
cfg = config.networking;
interfaces = attrValues cfg.interfaces;
slaves =
concatMap (i: i.interfaces) (attrValues cfg.bonds)
++ concatMap (i: i.interfaces) (attrValues cfg.bridges)
++ concatMap (i: attrNames (filterAttrs (_: config: config.type != "internal") i.interfaces)) (
attrValues cfg.vswitches
)
++ concatMap (i: [ i.interface ]) (attrValues cfg.macvlans)
++ concatMap (i: [ i.interface ]) (attrValues cfg.vlans);
# We must escape interfaces due to the systemd interpretation
subsystemDevice = interface: "sys-subsystem-net-devices-${escapeSystemdPath interface}.device";
interfaceIps = i: i.ipv4.addresses ++ optionals cfg.enableIPv6 i.ipv6.addresses;
destroyBond = i: ''
while true; do
UPDATED=1
SLAVES=$(ip link | grep 'master ${i}' | awk -F: '{print $2}')
for I in $SLAVES; do
UPDATED=0
ip link set dev "$I" nomaster
done
[ "$UPDATED" -eq "1" ] && break
done
ip link set dev "${i}" down 2>/dev/null || true
ip link del dev "${i}" 2>/dev/null || true
'';
formatIpArgs =
args:
lib.pipe args [
(lib.filterAttrs (n: v: v != null))
(lib.mapAttrsToList (n: v: "${n} ${toString v}"))
(lib.concatStringsSep " ")
];
# warn that these attributes are deprecated (2017-2-2)
# Should be removed in the release after next
bondDeprecation = rec {
deprecated = [
"lacp_rate"
"miimon"
"mode"
"xmit_hash_policy"
];
filterDeprecated =
bond: (filterAttrs (attrName: attr: elem attrName deprecated && attr != null) bond);
};
bondWarnings =
let
oneBondWarnings =
bondName: bond: mapAttrsToList (bondText bondName) (bondDeprecation.filterDeprecated bond);
bondText =
bondName: optName: _:
"${bondName}.${optName} is deprecated, use ${bondName}.driverOptions";
in
{
warnings = flatten (mapAttrsToList oneBondWarnings cfg.bonds);
};
normalConfig = {
systemd.network.links =
let
createNetworkLink =
i:
nameValuePair "40-${i.name}" {
matchConfig.OriginalName = i.name;
linkConfig =
optionalAttrs (i.macAddress != null) {
MACAddress = i.macAddress;
}
// optionalAttrs (i.mtu != null) {
MTUBytes = toString i.mtu;
};
};
in
listToAttrs (map createNetworkLink interfaces);
systemd.services =
let
deviceDependency =
dev:
# Use systemd service if we manage device creation, else
# trust udev when not in a container
if (dev == null || dev == "lo") then
[ ]
else if
(hasAttr dev (filterAttrs (k: v: v.virtual) cfg.interfaces))
|| (hasAttr dev cfg.bridges)
|| (hasAttr dev cfg.bonds)
|| (hasAttr dev cfg.macvlans)
|| (hasAttr dev cfg.sits)
|| (hasAttr dev cfg.ipips)
|| (hasAttr dev cfg.vlans)
|| (hasAttr dev cfg.greTunnels)
|| (hasAttr dev cfg.vswitches)
then
[ "${dev}-netdev.service" ]
else
optional (!config.boot.isContainer) (subsystemDevice dev);
hasDefaultGatewaySet =
(cfg.defaultGateway != null && cfg.defaultGateway.address != "")
|| (cfg.enableIPv6 && cfg.defaultGateway6 != null && cfg.defaultGateway6.address != "");
needNetworkSetup =
cfg.resolvconf.enable || cfg.defaultGateway != null || cfg.defaultGateway6 != null;
networkLocalCommands = lib.mkIf needNetworkSetup {
after = [ "network-setup.service" ];
bindsTo = [ "network-setup.service" ];
};
networkSetup = lib.mkIf needNetworkSetup {
description = "Networking Setup";
after = [ "network-pre.target" ];
before = [
"network.target"
"shutdown.target"
];
wants = [ "network.target" ];
# exclude bridges from the partOf relationship to fix container networking bug #47210
partOf = map (i: "network-addresses-${i.name}.service") (
filter (i: !(hasAttr i.name cfg.bridges)) interfaces
);
conflicts = [ "shutdown.target" ];
wantedBy = [ "multi-user.target" ] ++ optional hasDefaultGatewaySet "network-online.target";
unitConfig.ConditionCapability = "CAP_NET_ADMIN";
path = [ pkgs.iproute2 ];
serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
};
unitConfig.DefaultDependencies = false;
script = ''
${optionalString config.networking.resolvconf.enable ''
# Set the static DNS configuration, if given.
${pkgs.openresolv}/sbin/resolvconf -m 1 -a static <<EOF
${optionalString (cfg.nameservers != [ ] && cfg.domain != null) ''
domain ${cfg.domain}
''}
${optionalString (cfg.search != [ ]) ("search " + concatStringsSep " " cfg.search)}
${flip concatMapStrings cfg.nameservers (ns: ''
nameserver ${ns}
'')}
EOF
''}
# Set the default gateway
${flip concatMapStrings
[
{
version = "-4";
gateway = cfg.defaultGateway;
}
{
version = "-6";
gateway = cfg.defaultGateway6;
}
]
(
{ version, gateway }:
optionalString (gateway != null && gateway.address != "") ''
${optionalString (gateway.interface != null) ''
ip ${version} route replace ${gateway.address} proto static ${
formatIpArgs {
metric = gateway.metric;
dev = gateway.interface;
}
}
''}
ip ${version} route replace default proto static ${
formatIpArgs {
metric = gateway.metric;
via = gateway.address;
window = cfg.defaultGatewayWindowSize;
dev = gateway.interface;
src = gateway.source;
}
}
''
)
}
'';
};
# For each interface <foo>, create a job network-addresses-<foo>.service"
# that performs static address configuration. It has a "wants"
# dependency on <foo>.service, which is supposed to create
# the interface and need not exist (i.e. for hardware
# interfaces). It has a binds-to dependency on the actual
# network device, so it only gets started after the interface
# has appeared, and it's stopped when the interface
# disappears.
configureAddrs =
i:
let
ips = interfaceIps i;
in
nameValuePair "network-addresses-${i.name}" {
description = "Address configuration of ${i.name}";
wantedBy = [
"network-setup.service"
"network.target"
];
# order before network-setup because the routes that are configured
# there may need ip addresses configured
before = [ "network-setup.service" ];
bindsTo = deviceDependency i.name;
after = [ "network-pre.target" ] ++ (deviceDependency i.name);
serviceConfig.Type = "oneshot";
serviceConfig.RemainAfterExit = true;
# Restart rather than stop+start this unit to prevent the
# network from dying during switch-to-configuration.
stopIfChanged = false;
path = [ pkgs.iproute2 ];
script = ''
state="/run/nixos/network/addresses/${i.name}"
mkdir -p "$(dirname "$state")"
ip link set dev "${i.name}" up
${flip concatMapStrings ips (
ip:
let
cidr = "${ip.address}/${toString ip.prefixLength}";
in
''
echo "${cidr}" >> $state
echo -n "adding address ${cidr}... "
if out=$(ip addr replace "${cidr}" dev "${i.name}" nodad 2>&1); then
echo "done"
else
echo "'ip addr replace \"${cidr}\" dev \"${i.name}\"' nodad failed: $out"
exit 1
fi
''
)}
state="/run/nixos/network/routes/${i.name}"
mkdir -p "$(dirname "$state")"
${flip concatMapStrings (i.ipv4.routes ++ i.ipv6.routes) (
route:
let
cidr = "${route.address}/${toString route.prefixLength}";
via = optionalString (route.via != null) ''via "${route.via}"'';
options = concatStrings (mapAttrsToList (name: val: "${name} ${val} ") route.options);
type = toString route.type;
in
''
echo "${cidr}" >> $state
echo -n "adding route ${cidr}... "
if out=$(ip route add ${type} "${cidr}" ${options} ${via} dev "${i.name}" proto static 2>&1); then
echo "done"
elif ! echo "$out" | grep "File exists" >/dev/null 2>&1; then
echo "'ip route add ${type} \"${cidr}\" ${options} ${via} dev \"${i.name}\"' failed: $out"
exit 1
fi
''
)}
'';
preStop = ''
state="/run/nixos/network/routes/${i.name}"
if [ -e "$state" ]; then
while read -r cidr; do
echo -n "deleting route $cidr... "
ip route del "$cidr" dev "${i.name}" >/dev/null 2>&1 && echo "done" || echo "failed"
done < "$state"
rm -f "$state"
fi
state="/run/nixos/network/addresses/${i.name}"
if [ -e "$state" ]; then
while read -r cidr; do
echo -n "deleting address $cidr... "
ip addr del "$cidr" dev "${i.name}" >/dev/null 2>&1 && echo "done" || echo "failed"
done < "$state"
rm -f "$state"
fi
'';
};
createTunDevice =
i:
nameValuePair "${i.name}-netdev" {
description = "Virtual Network Interface ${i.name}";
bindsTo = optional (!config.boot.isContainer) "dev-net-tun.device";
after = optional (!config.boot.isContainer) "dev-net-tun.device" ++ [ "network-pre.target" ];
wantedBy = [
"network.target"
"network-setup.service"
(subsystemDevice i.name)
];
before = [ "network-setup.service" ];
path = [ pkgs.iproute2 ];
serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
};
script = ''
ip tuntap add dev "${i.name}" mode "${i.virtualType}" user "${i.virtualOwner}"
'';
postStop = ''
ip link del dev ${i.name} || true
'';
};
createBridgeDevice =
n: v:
nameValuePair "${n}-netdev" (
let
deps = concatLists (map deviceDependency v.interfaces);
in
{
description = "Bridge Interface ${n}";
wantedBy = [
"network.target"
"network-setup.service"
(subsystemDevice n)
];
bindsTo = deps ++ optional v.rstp "mstpd.service";
partOf = [ "network-setup.service" ] ++ optional v.rstp "mstpd.service";
after = [
"network-pre.target"
]
++ deps
++ optional v.rstp "mstpd.service"
++ map (i: "network-addresses-${i}.service") v.interfaces;
before = [ "network-setup.service" ];
serviceConfig.Type = "oneshot";
serviceConfig.RemainAfterExit = true;
path = [ pkgs.iproute2 ];
script = ''
# Remove Dead Interfaces
echo "Removing old bridge ${n}..."
ip link show dev "${n}" >/dev/null 2>&1 && ip link del dev "${n}"
echo "Adding bridge ${n}..."
ip link add name "${n}" type bridge
# Enslave child interfaces
${flip concatMapStrings v.interfaces (i: ''
ip link set dev "${i}" master "${n}"
ip link set dev "${i}" up
'')}
# Save list of enslaved interfaces
echo "${
flip concatMapStrings v.interfaces (i: ''
${i}
'')
}" > /run/${n}.interfaces
${optionalString config.virtualisation.libvirtd.enable ''
# Enslave dynamically added interfaces which may be lost on nixos-rebuild
#
# if `libvirtd.service` is not running, do not use `virsh` which would try activate it via 'libvirtd.socket' and thus start it out-of-order.
# `libvirtd.service` will set up bridge interfaces when it will start normally.
#
if /run/current-system/systemd/bin/systemctl --quiet is-active 'libvirtd.service'; then
for uri in qemu:///system lxc:///; do
for dom in $(${pkgs.libvirt}/bin/virsh -c $uri list --name); do
${pkgs.libvirt}/bin/virsh -c $uri dumpxml "$dom" | \
${pkgs.xmlstarlet}/bin/xmlstarlet sel -t -m "//domain/devices/interface[@type='bridge'][source/@bridge='${n}'][target/@dev]" -v "concat('ip link set dev ',target/@dev,' master ',source/@bridge,';')" | \
${pkgs.bash}/bin/bash
done
done
fi
''}
# Enable stp on the interface
${optionalString v.rstp ''
echo 2 >/sys/class/net/${n}/bridge/stp_state
''}
ip link set dev "${n}" up
'';
postStop = ''
ip link set dev "${n}" down || true
ip link del dev "${n}" || true
rm -f /run/${n}.interfaces
'';
reload = ''
# Un-enslave child interfaces (old list of interfaces)
for interface in `cat /run/${n}.interfaces`; do
ip link set dev "$interface" nomaster up
done
# Enslave child interfaces (new list of interfaces)
${flip concatMapStrings v.interfaces (i: ''
ip link set dev "${i}" master "${n}"
ip link set dev "${i}" up
'')}
# Save list of enslaved interfaces
echo "${
flip concatMapStrings v.interfaces (i: ''
${i}
'')
}" > /run/${n}.interfaces
# (Un-)set stp on the bridge
echo ${if v.rstp then "2" else "0"} > /sys/class/net/${n}/bridge/stp_state
'';
reloadIfChanged = true;
}
);
createVswitchDevice =
n: v:
nameValuePair "${n}-netdev" (
let
deps = concatLists (
map deviceDependency (attrNames (filterAttrs (_: config: config.type != "internal") v.interfaces))
);
internalConfigs = map (i: "network-addresses-${i}.service") (
attrNames (filterAttrs (_: config: config.type == "internal") v.interfaces)
);
ofRules = pkgs.writeText "vswitch-${n}-openFlowRules" v.openFlowRules;
in
{
description = "Open vSwitch Interface ${n}";
wantedBy = [
"network.target"
"network-setup.service"
(subsystemDevice n)
]
++ internalConfigs;
# before = [ "network-setup.service" ];
# should work without internalConfigs dependencies because address/link configuration depends
# on the device, which is created by ovs-vswitchd with type=internal, but it does not...
before = [ "network-setup.service" ] ++ internalConfigs;
partOf = [ "network-setup.service" ]; # shutdown the bridge when network is shutdown
bindsTo = [ "ovs-vswitchd.service" ]; # requires ovs-vswitchd to be alive at all times
after = [
"network-pre.target"
"ovs-vswitchd.service"
]
++ deps; # start switch after physical interfaces and vswitch daemon
wants = deps; # if one or more interface fails, the switch should continue to run
serviceConfig.Type = "oneshot";
serviceConfig.RemainAfterExit = true;
path = [
pkgs.iproute2
config.virtualisation.vswitch.package
];
preStart = ''
echo "Resetting Open vSwitch ${n}..."
ovs-vsctl --if-exists del-br ${n} -- add-br ${n} \
-- set bridge ${n} protocols=${concatStringsSep "," v.supportedOpenFlowVersions}
'';
script = ''
echo "Configuring Open vSwitch ${n}..."
ovs-vsctl ${
concatStrings (
mapAttrsToList (
name: config:
" -- add-port ${n} ${name}" + optionalString (config.vlan != null) " tag=${toString config.vlan}"
) v.interfaces
)
} \
${
concatStrings (
mapAttrsToList (
name: config: optionalString (config.type != null) " -- set interface ${name} type=${config.type}"
) v.interfaces
)
} \
${concatMapStrings (x: " -- set-controller ${n} " + x) v.controllers} \
${concatMapStrings (x: " -- " + x) (splitString "\n" v.extraOvsctlCmds)}
echo "Adding OpenFlow rules for Open vSwitch ${n}..."
ovs-ofctl --protocols=${v.openFlowVersion} add-flows ${n} ${ofRules}
'';
postStop = ''
echo "Cleaning Open vSwitch ${n}"
echo "Shutting down internal ${n} interface"
ip link set dev ${n} down || true
echo "Deleting flows for ${n}"
ovs-ofctl --protocols=${v.openFlowVersion} del-flows ${n} || true
echo "Deleting Open vSwitch ${n}"
ovs-vsctl --if-exists del-br ${n} || true
'';
}
);
createBondDevice =
n: v:
nameValuePair "${n}-netdev" (
let
deps = concatLists (map deviceDependency v.interfaces);
in
{
description = "Bond Interface ${n}";
wantedBy = [
"network.target"
"network-setup.service"
(subsystemDevice n)
];
bindsTo = deps;
after = [ "network-pre.target" ] ++ deps ++ map (i: "network-addresses-${i}.service") v.interfaces;
before = [ "network-setup.service" ];
serviceConfig.Type = "oneshot";
serviceConfig.RemainAfterExit = true;
path = [
pkgs.iproute2
pkgs.gawk
];
script = ''
echo "Destroying old bond ${n}..."
${destroyBond n}
echo "Creating new bond ${n}..."
ip link add name "${n}" type bond \
${
let
opts = (mapAttrs (const toString) (bondDeprecation.filterDeprecated v)) // v.driverOptions;
in
concatStringsSep "\n" (mapAttrsToList (set: val: " ${set} ${val} \\") opts)
}
# !!! There must be a better way to wait for the interface
while [ ! -d "/sys/class/net/${n}" ]; do sleep 0.1; done;
# Bring up the bond and enslave the specified interfaces
ip link set dev "${n}" up
${flip concatMapStrings v.interfaces (i: ''
ip link set dev "${i}" down
ip link set dev "${i}" master "${n}"
'')}
'';
postStop = destroyBond n;
}
);
createMacvlanDevice =
n: v:
nameValuePair "${n}-netdev" (
let
deps = deviceDependency v.interface;
in
{
description = "MACVLAN Interface ${n}";
wantedBy = [
"network.target"
"network-setup.service"
(subsystemDevice n)
];
bindsTo = deps;
after = [ "network-pre.target" ] ++ deps;
before = [ "network-setup.service" ];
serviceConfig.Type = "oneshot";
serviceConfig.RemainAfterExit = true;
path = [ pkgs.iproute2 ];
script = ''
# Remove Dead Interfaces
ip link show dev "${n}" >/dev/null 2>&1 && ip link delete dev "${n}"
ip link add link "${v.interface}" name "${n}" type macvlan \
${optionalString (v.mode != null) "mode ${v.mode}"}
ip link set dev "${n}" up
'';
postStop = ''
ip link delete dev "${n}" || true
'';
}
);
createFouEncapsulation =
n: v:
nameValuePair "${n}-fou-encap" (
let
# if we have a device to bind to we can wait for its addresses to be
# configured, otherwise external sequencing is required.
deps = optionals (v.local != null && v.local.dev != null) (
deviceDependency v.local.dev ++ [ "network-addresses-${v.local.dev}.service" ]
);
fouSpec = "port ${toString v.port} ${
if v.protocol != null then "ipproto ${toString v.protocol}" else "gue"
} ${
optionalString (v.local != null)
"local ${escapeShellArg v.local.address} ${
optionalString (v.local.dev != null) "dev ${escapeShellArg v.local.dev}"
}"
}";
in
{
description = "FOU endpoint ${n}";
wantedBy = [
"network.target"
"network-setup.service"
(subsystemDevice n)
];
bindsTo = deps;
after = [ "network-pre.target" ] ++ deps;
before = [ "network-setup.service" ];
serviceConfig.Type = "oneshot";
serviceConfig.RemainAfterExit = true;
path = [ pkgs.iproute2 ];
script = ''
# always remove previous incarnation since show can't filter
ip fou del ${fouSpec} >/dev/null 2>&1 || true
ip fou add ${fouSpec}
'';
postStop = ''
ip fou del ${fouSpec} || true
'';
}
);
createSitDevice =
n: v:
nameValuePair "${n}-netdev" (
let
deps = deviceDependency v.dev;
in
{
description = "IPv6 in IPv4 Tunnel Interface ${n}";
wantedBy = [
"network.target"
"network-setup.service"
(subsystemDevice n)
];
bindsTo = deps;
after = [ "network-pre.target" ] ++ deps;
before = [ "network-setup.service" ];
serviceConfig.Type = "oneshot";
serviceConfig.RemainAfterExit = true;
path = [ pkgs.iproute2 ];
script = ''
# Remove Dead Interfaces
ip link show dev "${n}" >/dev/null 2>&1 && ip link delete dev "${n}"
ip link add name "${n}" type sit ${
formatIpArgs {
inherit (v)
remote
local
ttl
dev
;
encap = if v.encapsulation.type == "6in4" then null else v.encapsulation.type;
encap-dport = v.encapsulation.port;
encap-sport = v.encapsulation.sourcePort;
}
}
ip link set dev "${n}" up
'';
postStop = ''
ip link delete dev "${n}" || true
'';
}
);
createIpipDevice =
n: v:
nameValuePair "${n}-netdev" (
let
deps = deviceDependency v.dev;
in
{
description = "IP in IP Tunnel Interface ${n}";
wantedBy = [
"network.target"
"network-setup.service"
(subsystemDevice n)
];
bindsTo = deps;
after = [ "network-pre.target" ] ++ deps;
before = [ "network-setup.service" ];
serviceConfig.Type = "oneshot";
serviceConfig.RemainAfterExit = true;
path = [ pkgs.iproute2 ];
script = ''
# Remove Dead Interfaces
ip link show dev "${n}" >/dev/null 2>&1 && ip link delete dev "${n}"
ip tunnel add name "${n}" ${
formatIpArgs {
inherit (v)
remote
local
ttl
dev
;
mode =
{
"4in6" = "ipip6";
"ipip" = "ipip";
}
.${v.encapsulation.type};
encaplimit = if v.encapsulation.type == "ipip" then null else v.encapsulation.limit;
}
}
ip link set dev "${n}" up
'';
postStop = ''
ip link delete dev "${n}" || true
'';
}
);
createGreDevice =
n: v:
nameValuePair "${n}-netdev" (
let
deps = deviceDependency v.dev;
ttlarg = if lib.hasPrefix "ip6" v.type then "hoplimit" else "ttl";
in
{
description = "GRE Tunnel Interface ${n}";
wantedBy = [
"network.target"
"network-setup.service"
(subsystemDevice n)
];
bindsTo = deps;
after = [ "network-pre.target" ] ++ deps;
before = [ "network-setup.service" ];
serviceConfig.Type = "oneshot";
serviceConfig.RemainAfterExit = true;
path = [ pkgs.iproute2 ];
script = ''
# Remove Dead Interfaces
ip link show dev "${n}" >/dev/null 2>&1 && ip link delete dev "${n}"
ip link add name "${n}" type ${v.type} \
${optionalString (v.remote != null) "remote \"${v.remote}\""} \
${optionalString (v.local != null) "local \"${v.local}\""} \
${optionalString (v.ttl != null) "${ttlarg} ${toString v.ttl}"} \
${optionalString (v.dev != null) "dev \"${v.dev}\""}
ip link set dev "${n}" up
'';
postStop = ''
ip link delete dev "${n}" || true
'';
}
);
createVlanDevice =
n: v:
nameValuePair "${n}-netdev" (
let
deps = deviceDependency v.interface;
in
{
description = "VLAN Interface ${n}";
wantedBy = [
"network.target"
"network-setup.service"
(subsystemDevice n)
];
bindsTo = deps;
partOf = [ "network-setup.service" ];
after = [ "network-pre.target" ] ++ deps;
before = [ "network-setup.service" ];
serviceConfig.Type = "oneshot";
serviceConfig.RemainAfterExit = true;
path = [ pkgs.iproute2 ];
script = ''
# Remove Dead Interfaces
ip link show dev "${n}" >/dev/null 2>&1 && ip link delete dev "${n}"
ip link add link "${v.interface}" name "${n}" type vlan id "${toString v.id}"
# We try to bring up the logical VLAN interface. If the master
# interface the logical interface is dependent upon is not up yet we will
# fail to immediately bring up the logical interface. The resulting logical
# interface will brought up later when the master interface is up.
ip link set dev "${n}" up || true
'';
postStop = ''
ip link delete dev "${n}" || true
'';
}
);
in
listToAttrs (
map configureAddrs interfaces ++ map createTunDevice (filter (i: i.virtual) interfaces)
)
// mapAttrs' createBridgeDevice cfg.bridges
// mapAttrs' createVswitchDevice cfg.vswitches
// mapAttrs' createBondDevice cfg.bonds
// mapAttrs' createMacvlanDevice cfg.macvlans
// mapAttrs' createFouEncapsulation cfg.fooOverUDP
// mapAttrs' createSitDevice cfg.sits
// mapAttrs' createIpipDevice cfg.ipips
// mapAttrs' createGreDevice cfg.greTunnels
// mapAttrs' createVlanDevice cfg.vlans
// {
network-setup = networkSetup;
network-local-commands = networkLocalCommands;
};
services.udev.extraRules = ''
KERNEL=="tun", TAG+="systemd"
'';
};
in
{
meta.maintainers = with lib.maintainers; [ rnhmjoj ];
config = mkMerge [
bondWarnings
(mkIf (!cfg.useNetworkd) normalConfig)
{
# Ensure slave interfaces are brought up
networking.interfaces = genAttrs slaves (i: { });
}
];
}

View File

@@ -0,0 +1,614 @@
{
config,
lib,
utils,
pkgs,
...
}:
with utils;
with lib;
let
cfg = config.networking;
interfaces = attrValues cfg.interfaces;
interfaceIps = i: i.ipv4.addresses ++ optionals cfg.enableIPv6 i.ipv6.addresses;
interfaceRoutes = i: i.ipv4.routes ++ optionals cfg.enableIPv6 i.ipv6.routes;
dhcpStr = useDHCP: if useDHCP == true || useDHCP == null then "yes" else "no";
slaves =
concatLists (map (bond: bond.interfaces) (attrValues cfg.bonds))
++ concatLists (map (bridge: bridge.interfaces) (attrValues cfg.bridges))
++ map (sit: sit.dev) (attrValues cfg.sits)
++ map (ipip: ipip.dev) (attrValues cfg.ipips)
++ map (gre: gre.dev) (attrValues cfg.greTunnels)
++ map (vlan: vlan.interface) (attrValues cfg.vlans)
# add dependency to physical or independently created vswitch member interface
# TODO: warn the user that any address configured on those interfaces will be useless
++ concatMap (i: attrNames (filterAttrs (_: config: config.type != "internal") i.interfaces)) (
attrValues cfg.vswitches
);
defaultGateways = mkMerge (
forEach [ cfg.defaultGateway cfg.defaultGateway6 ] (
gateway:
optionalAttrs (gateway != null && gateway.interface != null) {
networks."40-${gateway.interface}" = {
matchConfig.Name = gateway.interface;
routes = [
(
{
Gateway = gateway.address;
}
// optionalAttrs (gateway.metric != null) {
Metric = gateway.metric;
}
// optionalAttrs (gateway.source != null) {
PreferredSource = gateway.source;
}
)
];
};
}
)
);
genericDhcpNetworks = mkIf cfg.useDHCP {
networks."99-ethernet-default-dhcp" = {
matchConfig = {
Type = "ether";
Kind = "!*"; # physical interfaces have no kind
};
DHCP = "yes";
networkConfig.IPv6PrivacyExtensions = "kernel";
};
networks."99-wireless-client-dhcp" = {
matchConfig.WLANInterfaceType = "station";
DHCP = "yes";
networkConfig.IPv6PrivacyExtensions = "kernel";
# We also set the route metric to one more than the default
# of 1024, so that Ethernet is preferred if both are
# available.
dhcpV4Config.RouteMetric = 1025;
ipv6AcceptRAConfig.RouteMetric = 1025;
};
};
interfaceNetworks = mkMerge (
forEach interfaces (i: {
links = mkIf i.wakeOnLan.enable {
"40-${i.name}" = {
matchConfig.name = i.name;
linkConfig.WakeOnLan = concatStringsSep " " i.wakeOnLan.policy;
};
};
netdevs = mkIf i.virtual {
"40-${i.name}" = {
netdevConfig = {
Name = i.name;
Kind = i.virtualType;
};
"${i.virtualType}Config" = optionalAttrs (i.virtualOwner != null) {
User = i.virtualOwner;
};
};
};
networks."40-${i.name}" = {
name = mkDefault i.name;
DHCP = mkForce (
dhcpStr (
if i.useDHCP != null then i.useDHCP else (config.networking.useDHCP && i.ipv4.addresses == [ ])
)
);
address = forEach (interfaceIps i) (ip: "${ip.address}/${toString ip.prefixLength}");
routes = forEach (interfaceRoutes i) (
route:
mkMerge [
# Most of these route options have not been tested.
# Please fix or report any mistakes you may find.
(mkIf (route.address != null && route.prefixLength != null) {
Destination = "${route.address}/${toString route.prefixLength}";
})
(mkIf (route.options ? fastopen_no_cookie) {
FastOpenNoCookie = route.options.fastopen_no_cookie;
})
(mkIf (route.via != null) {
Gateway = route.via;
})
(mkIf (route.type != null) {
Type = route.type;
})
(mkIf (route.options ? onlink) {
GatewayOnLink = true;
})
(mkIf (route.options ? initrwnd) {
InitialAdvertisedReceiveWindow = route.options.initrwnd;
})
(mkIf (route.options ? initcwnd) {
InitialCongestionWindow = route.options.initcwnd;
})
(mkIf (route.options ? pref) {
IPv6Preference = route.options.pref;
})
(mkIf (route.options ? mtu) {
MTUBytes = route.options.mtu;
})
(mkIf (route.options ? metric) {
Metric = route.options.metric;
})
(mkIf (route.options ? src) {
PreferredSource = route.options.src;
})
(mkIf (route.options ? protocol) {
Protocol = route.options.protocol;
})
(mkIf (route.options ? quickack) {
QuickAck = route.options.quickack;
})
(mkIf (route.options ? scope) {
Scope = route.options.scope;
})
(mkIf (route.options ? from) {
Source = route.options.from;
})
(mkIf (route.options ? table) {
Table = route.options.table;
})
(mkIf (route.options ? advmss) {
TCPAdvertisedMaximumSegmentSize = route.options.advmss;
})
(mkIf (route.options ? ttl-propagate) {
TTLPropagate = route.options.ttl-propagate == "enabled";
})
]
);
networkConfig.IPv6PrivacyExtensions = "kernel";
linkConfig =
optionalAttrs (i.macAddress != null) {
MACAddress = i.macAddress;
}
// optionalAttrs (i.mtu != null) {
MTUBytes = toString i.mtu;
};
bridgeConfig = optionalAttrs i.proxyARP {
ProxyARP = i.proxyARP;
};
};
})
);
bridgeNetworks = mkMerge (
flip mapAttrsToList cfg.bridges (
name: bridge: {
netdevs."40-${name}" = {
netdevConfig = {
Name = name;
Kind = "bridge";
};
};
networks = listToAttrs (
forEach bridge.interfaces (
bi:
nameValuePair "40-${bi}" {
DHCP = mkOverride 0 (dhcpStr false);
networkConfig.Bridge = name;
}
)
);
}
)
);
vlanNetworks = mkMerge (
flip mapAttrsToList cfg.vlans (
name: vlan: {
netdevs."40-${name}" = {
netdevConfig = {
Name = name;
Kind = "vlan";
};
vlanConfig.Id = vlan.id;
};
networks."40-${vlan.interface}" = {
vlan = [ name ];
};
}
)
);
in
{
config = mkMerge [
(mkIf config.boot.initrd.network.enable {
# Note this is if initrd.network.enable, not if
# initrd.systemd.network.enable. By setting the latter and not the
# former, the user retains full control over the configuration.
boot.initrd.systemd.network = mkMerge [
defaultGateways
genericDhcpNetworks
interfaceNetworks
bridgeNetworks
vlanNetworks
];
boot.initrd.availableKernelModules =
optional (cfg.bridges != { }) "bridge" ++ optional (cfg.vlans != { }) "8021q";
})
(mkIf cfg.useNetworkd {
assertions = [
{
assertion = cfg.defaultGatewayWindowSize == null;
message = "networking.defaultGatewayWindowSize is not supported by networkd.";
}
{
assertion = cfg.defaultGateway != null -> cfg.defaultGateway.interface != null;
message = "networking.defaultGateway.interface is not optional when using networkd.";
}
{
assertion = cfg.defaultGateway6 != null -> cfg.defaultGateway6.interface != null;
message = "networking.defaultGateway6.interface is not optional when using networkd.";
}
]
++ flip mapAttrsToList cfg.bridges (
n:
{ rstp, ... }:
{
assertion = !rstp;
message = "networking.bridges.${n}.rstp is not supported by networkd.";
}
)
++ flip mapAttrsToList cfg.fooOverUDP (
n:
{ local, ... }:
{
assertion = local == null;
message = "networking.fooOverUDP.${n}.local is not supported by networkd.";
}
);
networking.dhcpcd.enable = mkDefault false;
systemd.network = mkMerge [
{
enable = true;
}
defaultGateways
genericDhcpNetworks
interfaceNetworks
bridgeNetworks
(mkMerge (
flip mapAttrsToList cfg.bonds (
name: bond: {
netdevs."40-${name}" = {
netdevConfig = {
Name = name;
Kind = "bond";
};
bondConfig =
let
# manual mapping as of 2017-02-03
# man 5 systemd.netdev [BOND]
# to https://www.kernel.org/doc/Documentation/networking/bonding.txt
# driver options.
driverOptionMapping =
let
trans = f: optName: {
valTransform = f;
optNames = [ optName ];
};
simp = trans id;
ms = trans (v: v + "ms");
in
{
Mode = simp "mode";
TransmitHashPolicy = simp "xmit_hash_policy";
LACPTransmitRate = simp "lacp_rate";
MIIMonitorSec = ms "miimon";
UpDelaySec = ms "updelay";
DownDelaySec = ms "downdelay";
LearnPacketIntervalSec = simp "lp_interval";
AdSelect = simp "ad_select";
FailOverMACPolicy = simp "fail_over_mac";
ARPValidate = simp "arp_validate";
# apparently in ms for this value?! Upstream bug?
ARPIntervalSec = simp "arp_interval";
ARPIPTargets = simp "arp_ip_target";
ARPAllTargets = simp "arp_all_targets";
PrimaryReselectPolicy = simp "primary_reselect";
ResendIGMP = simp "resend_igmp";
PacketsPerSlave = simp "packets_per_slave";
GratuitousARP = {
valTransform = id;
optNames = [
"num_grat_arp"
"num_unsol_na"
];
};
AllSlavesActive = simp "all_slaves_active";
MinLinks = simp "min_links";
};
do = bond.driverOptions;
assertNoUnknownOption =
let
knownOptions = flatten (mapAttrsToList (_: kOpts: kOpts.optNames) driverOptionMapping);
# options that apparently dont exist in the networkd config
unknownOptions = [ "primary" ];
assertTrace = bool: msg: if bool then true else builtins.trace msg false;
in
assert all (
driverOpt:
assertTrace (elem driverOpt (knownOptions ++ unknownOptions))
"The bond.driverOption `${driverOpt}` cannot be mapped to the list of known networkd bond options. Please add it to the mapping above the assert or to `unknownOptions` should it not exist in networkd."
) (mapAttrsToList (k: _: k) do);
"";
# get those driverOptions that have been set
filterSystemdOptions = filterAttrs (sysDOpt: kOpts: any (kOpt: do ? ${kOpt}) kOpts.optNames);
# build final set of systemd options to bond values
buildOptionSet = mapAttrs (
_: kOpts:
with kOpts;
# we simply take the first set kernel bond option
# (one option has multiple names, which is silly)
head (
map (optN: valTransform (do.${optN}))
# only map those that exist
(filter (o: do ? ${o}) optNames)
)
);
in
seq assertNoUnknownOption (buildOptionSet (filterSystemdOptions driverOptionMapping));
};
networks = listToAttrs (
forEach bond.interfaces (
bi:
nameValuePair "40-${bi}" {
DHCP = mkOverride 0 (dhcpStr false);
networkConfig.Bond = name;
}
)
);
}
)
))
(mkMerge (
flip mapAttrsToList cfg.macvlans (
name: macvlan: {
netdevs."40-${name}" = {
netdevConfig = {
Name = name;
Kind = "macvlan";
};
macvlanConfig = optionalAttrs (macvlan.mode != null) { Mode = macvlan.mode; };
};
networks."40-${macvlan.interface}" = {
macvlan = [ name ];
};
}
)
))
(mkMerge (
flip mapAttrsToList cfg.fooOverUDP (
name: fou: {
netdevs."40-${name}" = {
netdevConfig = {
Name = name;
Kind = "fou";
};
# unfortunately networkd cannot encode dependencies of netdevs on addresses/routes,
# so we cannot specify Local=, Peer=, PeerPort=. this looks like a missing feature
# in networkd.
fooOverUDPConfig = {
Port = fou.port;
Encapsulation = if fou.protocol != null then "FooOverUDP" else "GenericUDPEncapsulation";
}
// (optionalAttrs (fou.protocol != null) {
Protocol = fou.protocol;
});
};
}
)
))
(mkMerge (
flip mapAttrsToList cfg.sits (
name: sit: {
netdevs."40-${name}" = {
netdevConfig = {
Name = name;
Kind = "sit";
};
tunnelConfig =
(optionalAttrs (sit.remote != null) {
Remote = sit.remote;
})
// (optionalAttrs (sit.local != null) {
Local = sit.local;
})
// (optionalAttrs (sit.ttl != null) {
TTL = sit.ttl;
})
// (optionalAttrs (sit.encapsulation.type != "6in4") (
{
FooOverUDP = true;
Encapsulation = if sit.encapsulation.type == "fou" then "FooOverUDP" else "GenericUDPEncapsulation";
FOUDestinationPort = sit.encapsulation.port;
}
// (optionalAttrs (sit.encapsulation.sourcePort != null) {
FOUSourcePort = sit.encapsulation.sourcePort;
})
));
};
networks = mkIf (sit.dev != null) {
"40-${sit.dev}" = {
tunnel = [ name ];
};
};
}
)
))
(mkMerge (
flip mapAttrsToList cfg.ipips (
name: ipip: {
netdevs."40-${name}" = {
netdevConfig = {
Name = name;
Kind = if ipip.encapsulation.type == "ipip" then "ipip" else "ip6tnl";
};
tunnelConfig =
(optionalAttrs (ipip.remote != null) {
Remote = ipip.remote;
})
// (optionalAttrs (ipip.local != null) {
Local = ipip.local;
})
// (optionalAttrs (ipip.ttl != null) {
TTL = ipip.ttl;
})
// (optionalAttrs (ipip.encapsulation.type != "ipip") {
# IPv6 tunnel options
Mode = if ipip.encapsulation.type == "4in6" then "ipip6" else "ip6ip6";
EncapsulationLimit = ipip.encapsulation.type;
});
};
networks = mkIf (ipip.dev != null) {
"40-${ipip.dev}" = {
tunnel = [ name ];
};
};
}
)
))
(mkMerge (
flip mapAttrsToList cfg.greTunnels (
name: gre: {
netdevs."40-${name}" = {
netdevConfig = {
Name = name;
Kind = gre.type;
};
tunnelConfig =
(optionalAttrs (gre.remote != null) {
Remote = gre.remote;
})
// (optionalAttrs (gre.local != null) {
Local = gre.local;
})
// (optionalAttrs (gre.ttl != null) {
TTL = gre.ttl;
});
};
networks = mkIf (gre.dev != null) {
"40-${gre.dev}" = {
tunnel = [ name ];
};
};
}
)
))
vlanNetworks
];
# We need to prefill the slaved devices with networking options
# This forces the network interface creator to initialize slaves.
networking.interfaces = listToAttrs (map (i: nameValuePair i { }) slaves);
systemd.services =
let
# We must escape interfaces due to the systemd interpretation
subsystemDevice = interface: "sys-subsystem-net-devices-${escapeSystemdPath interface}.device";
# support for creating openvswitch switches
createVswitchDevice =
n: v:
nameValuePair "${n}-netdev" (
let
deps = map subsystemDevice (
attrNames (filterAttrs (_: config: config.type != "internal") v.interfaces)
);
ofRules = pkgs.writeText "vswitch-${n}-openFlowRules" v.openFlowRules;
in
{
description = "Open vSwitch Interface ${n}";
wantedBy = [
"network.target"
(subsystemDevice n)
];
# and create bridge before systemd-networkd starts because it might create internal interfaces
before = [ "systemd-networkd.service" ];
# shutdown the bridge when network is shutdown
partOf = [ "network.target" ];
# requires ovs-vswitchd to be alive at all times
bindsTo = [ "ovs-vswitchd.service" ];
# start switch after physical interfaces and vswitch daemon
after = [
"network-pre.target"
"ovs-vswitchd.service"
]
++ deps;
wants = deps; # if one or more interface fails, the switch should continue to run
serviceConfig.Type = "oneshot";
serviceConfig.RemainAfterExit = true;
path = [
pkgs.iproute2
config.virtualisation.vswitch.package
];
preStart = ''
echo "Resetting Open vSwitch ${n}..."
ovs-vsctl --if-exists del-br ${n} -- add-br ${n} \
-- set bridge ${n} protocols=${concatStringsSep "," v.supportedOpenFlowVersions}
'';
script = ''
echo "Configuring Open vSwitch ${n}..."
ovs-vsctl ${
concatStrings (
mapAttrsToList (
name: config:
" -- add-port ${n} ${name}" + optionalString (config.vlan != null) " tag=${toString config.vlan}"
) v.interfaces
)
} \
${
concatStrings (
mapAttrsToList (
name: config: optionalString (config.type != null) " -- set interface ${name} type=${config.type}"
) v.interfaces
)
} \
${concatMapStrings (x: " -- set-controller ${n} " + x) v.controllers} \
${concatMapStrings (x: " -- " + x) (splitString "\n" v.extraOvsctlCmds)}
echo "Adding OpenFlow rules for Open vSwitch ${n}..."
ovs-ofctl --protocols=${v.openFlowVersion} add-flows ${n} ${ofRules}
'';
postStop = ''
echo "Cleaning Open vSwitch ${n}"
echo "Shutting down internal ${n} interface"
ip link set dev ${n} down || true
echo "Deleting flows for ${n}"
ovs-ofctl --protocols=${v.openFlowVersion} del-flows ${n} || true
echo "Deleting Open vSwitch ${n}"
ovs-vsctl --if-exists del-br ${n} || true
'';
}
);
in
mapAttrs' createVswitchDevice cfg.vswitches
// {
"network-local-commands" = {
after = [ "systemd-networkd.service" ];
bindsTo = [ "systemd-networkd.service" ];
};
};
})
];
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,69 @@
{
config,
lib,
pkgs,
...
}:
with lib;
let
cfg = config.powerManagement.powertop;
in
{
###### interface
options.powerManagement.powertop = {
enable = mkEnableOption "powertop auto tuning on startup";
preStart = mkOption {
type = types.lines;
default = "";
description = ''
Shell commands executed before `powertop` is started.
'';
};
postStart = mkOption {
type = types.lines;
default = "";
example = ''
''${lib.getExe' config.systemd.package "udevadm"} trigger -c bind -s usb -a idVendor=046d -a idProduct=c08c
'';
description = ''
Shell commands executed after `powertop` is started.
This can be used to workaround problematic configurations. For example,
you can retrigger an `udev` rule to disable power saving on unsupported
USB devices:
```
services.udev.extraRules = ''''
# disable USB auto suspend for Logitech, Inc. G PRO Gaming Mouse
ACTION=="bind", SUBSYSTEM=="usb", ATTR{idVendor}=="046d", ATTR{idProduct}=="c08c", TEST=="power/control", ATTR{power/control}="on"
'''';
```
'';
};
};
###### implementation
config = mkIf (cfg.enable) {
systemd.services = {
powertop = {
documentation = [ "man:powertop(8)" ];
wantedBy = [ "multi-user.target" ];
after = [ "multi-user.target" ];
description = "Powertop tunings";
path = [ pkgs.kmod ];
preStart = cfg.preStart;
postStart = cfg.postStart;
serviceConfig = {
Type = "oneshot";
RemainAfterExit = "yes";
ExecStart = "${pkgs.powertop}/bin/powertop --auto-tune";
};
};
};
};
}

View File

@@ -0,0 +1,53 @@
{ config, lib, ... }:
with lib;
let
cfg = config.powerManagement.scsiLinkPolicy;
kernel = config.boot.kernelPackages.kernel;
allowedValues = [
"min_power"
"max_performance"
"medium_power"
"med_power_with_dipm"
];
in
{
###### interface
options = {
powerManagement.scsiLinkPolicy = mkOption {
default = null;
type = types.nullOr (types.enum allowedValues);
description = ''
SCSI link power management policy. The kernel default is
"max_performance".
"med_power_with_dipm" is supported by kernel versions
4.15 and newer.
'';
};
};
###### implementation
config = mkIf (cfg != null) {
assertions = singleton {
assertion = (cfg == "med_power_with_dipm") -> versionAtLeast kernel.version "4.15";
message = "med_power_with_dipm is not supported for kernels older than 4.15";
};
services.udev.extraRules = ''
SUBSYSTEM=="scsi_host", ACTION=="add", KERNEL=="host*", ATTR{link_power_management_policy}="${cfg}"
'';
};
}

View File

@@ -0,0 +1,23 @@
{
config,
lib,
pkgs,
...
}:
let
cfg = config.services.stratis;
in
{
options.services.stratis = {
enable = lib.mkEnableOption "Stratis Storage - Easy to use local storage management for Linux";
};
config = lib.mkIf cfg.enable {
environment.systemPackages = [ pkgs.stratis-cli ];
systemd.packages = [ pkgs.stratisd ];
services.dbus.packages = [ pkgs.stratisd ];
services.udev.packages = [ pkgs.stratisd ];
systemd.services.stratisd.wantedBy = [ "sysinit.target" ];
};
}

View File

@@ -0,0 +1,107 @@
{
config,
pkgs,
lib,
...
}:
let
cfg = config.boot.swraid;
mdadm_conf = config.environment.etc."mdadm.conf";
enable_implicitly_for_old_state_versions = lib.versionOlder config.system.stateVersion "23.11";
minimum_config_is_set =
config_text: (builtins.match ".*(MAILADDR|PROGRAM).*" mdadm_conf.text) != null;
in
{
imports = [
(lib.mkRenamedOptionModule
[ "boot" "initrd" "services" "swraid" "enable" ]
[ "boot" "swraid" "enable" ]
)
(lib.mkRenamedOptionModule
[ "boot" "initrd" "services" "swraid" "mdadmConf" ]
[ "boot" "swraid" "mdadmConf" ]
)
];
options.boot.swraid = {
enable = lib.mkEnableOption "swraid support using mdadm" // {
description = ''
Whether to enable support for Linux MD RAID arrays.
When this is enabled, mdadm will be added to the system path,
and MD RAID arrays will be detected and activated
automatically, both in stage-1 (initramfs) and in stage-2 (the
final NixOS system).
This should be enabled if you want to be able to access and/or
boot from MD RAID arrays. {command}`nixos-generate-config`
should detect it correctly in the standard installation
procedure.
'';
default = enable_implicitly_for_old_state_versions;
defaultText = "`true` if stateVersion is older than 23.11";
};
mdadmConf = lib.mkOption {
description = "Contents of {file}`/etc/mdadm.conf`.";
type = lib.types.lines;
default = "";
};
};
config = lib.mkIf cfg.enable {
warnings =
lib.mkIf (!enable_implicitly_for_old_state_versions && !minimum_config_is_set mdadm_conf)
[
"mdadm: Neither MAILADDR nor PROGRAM has been set. This will cause the `mdmon` service to crash."
];
environment.systemPackages = [ pkgs.mdadm ];
environment.etc."mdadm.conf".text = lib.mkAfter cfg.mdadmConf;
services.udev.packages = [ pkgs.mdadm ];
systemd.packages = [ pkgs.mdadm ];
boot.initrd = {
availableKernelModules = [
"md_mod"
"raid0"
"raid1"
"raid10"
"raid456"
];
extraUdevRulesCommands = lib.mkIf (!config.boot.initrd.systemd.enable) ''
cp -v ${pkgs.mdadm}/lib/udev/rules.d/*.rules $out/
'';
extraUtilsCommands = lib.mkIf (!config.boot.initrd.systemd.enable) ''
# Add RAID mdadm tool.
copy_bin_and_libs ${pkgs.mdadm}/sbin/mdadm
copy_bin_and_libs ${pkgs.mdadm}/sbin/mdmon
'';
extraUtilsCommandsTest = lib.mkIf (!config.boot.initrd.systemd.enable) ''
$out/bin/mdadm --version
'';
extraFiles."/etc/mdadm.conf".source = pkgs.writeText "mdadm.conf" mdadm_conf.text;
systemd = {
contents."/etc/mdadm.conf".text = mdadm_conf.text;
packages = [ pkgs.mdadm ];
initrdBin = [ pkgs.mdadm ];
};
services.udev.packages = [ pkgs.mdadm ];
};
};
}

View File

@@ -0,0 +1,123 @@
{ config, lib, ... }:
with lib;
{
###### interface
options = {
hardware.trackpoint = {
enable = mkOption {
default = false;
type = types.bool;
description = ''
Enable sensitivity and speed configuration for trackpoints.
'';
};
sensitivity = mkOption {
default = 128;
example = 255;
type = types.int;
description = ''
Configure the trackpoint sensitivity. By default, the kernel
configures 128.
'';
};
speed = mkOption {
default = 97;
example = 255;
type = types.int;
description = ''
Configure the trackpoint speed. By default, the kernel
configures 97.
'';
};
emulateWheel = mkOption {
default = false;
type = types.bool;
description = ''
Enable scrolling while holding the middle mouse button.
'';
};
fakeButtons = mkOption {
default = false;
type = types.bool;
description = ''
Switch to "bare" PS/2 mouse support in case Trackpoint buttons are not recognized
properly. This can happen for example on models like the L430, T450, T450s, on
which the Trackpoint buttons are actually a part of the Synaptics touchpad.
'';
};
device = mkOption {
default = "TPPS/2 IBM TrackPoint";
type = types.str;
description = ''
The device name of the trackpoint. You can check with xinput.
Some newer devices (example x1c6) use "TPPS/2 Elan TrackPoint".
'';
};
};
};
###### implementation
config =
let
cfg = config.hardware.trackpoint;
in
mkMerge [
(mkIf cfg.enable {
services.udev.extraRules = ''
ACTION=="add|change", SUBSYSTEM=="input", ATTR{name}=="${cfg.device}", ATTR{device/speed}="${toString cfg.speed}", ATTR{device/sensitivity}="${toString cfg.sensitivity}"
'';
systemd.services.trackpoint = {
wantedBy = [ "sysinit.target" ];
before = [
"sysinit.target"
"shutdown.target"
];
conflicts = [ "shutdown.target" ];
unitConfig.DefaultDependencies = false;
serviceConfig.Type = "oneshot";
serviceConfig.RemainAfterExit = true;
serviceConfig.ExecStart = ''
${config.systemd.package}/bin/udevadm trigger --attr-match=name="${cfg.device}"
'';
};
})
(mkIf (cfg.emulateWheel) {
services.xserver.inputClassSections = [
''
Identifier "Trackpoint Wheel Emulation"
MatchProduct "${
if cfg.fakeButtons then
"PS/2 Generic Mouse"
else
"ETPS/2 Elantech TrackPoint|Elantech PS/2 TrackPoint|TPPS/2 IBM TrackPoint|DualPoint Stick|Synaptics Inc. Composite TouchPad / TrackPoint|ThinkPad USB Keyboard with TrackPoint|USB Trackpoint pointing device|Composite TouchPad / TrackPoint|${cfg.device}"
}"
MatchDevicePath "/dev/input/event*"
Option "EmulateWheel" "true"
Option "EmulateWheelButton" "2"
Option "Emulate3Buttons" "false"
Option "XAxisMapping" "6 7"
Option "YAxisMapping" "4 5"
''
];
})
(mkIf cfg.fakeButtons {
boot.extraModprobeConfig = "options psmouse proto=bare";
})
];
}