uboot: (firmwareOdroidC2/C4) don't invoke patch tool, use patches = [] instead

https://github.com/NixOS/nixpkgs/blob/master/pkgs/stdenv/generic/setup.sh#L948
this can do it nicely.

Signed-off-by: Anton Arapov <anton@deadbeef.mx>
This commit is contained in:
Anton Arapov 2021-04-03 12:58:10 +02:00 committed by Alan Daniels
commit 56de2bcd43
30691 changed files with 3076956 additions and 0 deletions

View file

@ -0,0 +1,253 @@
{ config, lib, pkgs, ... }:
with lib;
let cfg = config.system.autoUpgrade;
in {
options = {
system.autoUpgrade = {
enable = mkOption {
type = types.bool;
default = false;
description = ''
Whether to periodically upgrade NixOS to the latest
version. If enabled, a systemd timer will run
<literal>nixos-rebuild switch --upgrade</literal> once a
day.
'';
};
flake = mkOption {
type = types.nullOr 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</option>.
'';
};
channel = mkOption {
type = types.nullOr 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</command> (run <literal>nix-channel
--list</literal> to see the current value).
'';
};
flags = mkOption {
type = types.listOf 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</command>.
If you are using flakes and use a local repo you can add
<command>[ "--update-input" "nixpkgs" "--commit-lock-file" ]</command>
to update nixpkgs.
'';
};
dates = mkOption {
type = 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
<citerefentry><refentrytitle>systemd.time</refentrytitle>
<manvolnum>7</manvolnum></citerefentry>.
'';
};
allowReboot = mkOption {
default = false;
type = 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</option> for configuring the times at which a reboot is allowed.
'';
};
randomizedDelaySec = mkOption {
default = "0";
type = 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
<citerefentry><refentrytitle>systemd.time</refentrytitle>
<manvolnum>7</manvolnum></citerefentry>
'';
};
rebootWindow = 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</option> is enabled.
The default value of <literal>null</literal> means that reboots are allowed at any time.
'';
default = null;
example = { lower = "01:00"; upper = "05:00"; };
type = with types; nullOr (submodule {
options = {
lower = mkOption {
description = "Lower limit of the reboot window";
type = types.strMatching "[[:digit:]]{2}:[[:digit:]]{2}";
example = "01:00";
};
upper = mkOption {
description = "Upper limit of the reboot window";
type = types.strMatching "[[:digit:]]{2}:[[:digit:]]{2}";
example = "05:00";
};
};
});
};
persistent = mkOption {
default = true;
type = 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.channels' and 'system.autoUpgrade.flake' cannot both be set.
'';
}];
system.autoUpgrade.flags = (if cfg.flake == null then
[ "--no-build-output" ] ++ optionals (cfg.channel != null) [
"-I"
"nixpkgs=${cfg.channel}/nixexprs.tar.xz"
]
else
[ "--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 = optional (cfg.channel == null) "--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})"
${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} switch ${toString cfg.flags}
${optionalString (cfg.rebootWindow != null) ''
elif [ "''${do_reboot}" != true ]; then
echo "Outside of configured reboot window, skipping."
''}
else
${shutdown} -r +1
fi
'' else ''
${nixos-rebuild} switch ${toString (cfg.flags ++ upgradeFlag)}
'';
startAt = cfg.dates;
after = [ "network-online.target" ];
wants = [ "network-online.target" ];
};
systemd.timers.nixos-upgrade = {
timerConfig = {
RandomizedDelaySec = cfg.randomizedDelaySec;
Persistent = cfg.persistent;
};
};
};
}

View file

@ -0,0 +1,23 @@
{ config, lib, pkgs, ... }:
{
options.boot.initrd.services.bcache.enable = (lib.mkEnableOption "bcache support in the initrd") // {
visible = false; # only works with systemd stage 1
};
config = {
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,90 @@
{ 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,87 @@
{ config, lib, ... }:
with lib;
let
fileSystems = config.system.build.fileSystems ++ config.swapDevices;
encDevs = filter (dev: dev.encrypted.enable) fileSystems;
keyedEncDevs = filter (dev: dev.encrypted.keyFile != null) encDevs;
keylessEncDevs = 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 <literal>fileSystems.&lt;name?&gt;.device</literal> to <literal>/dev/mapper/&lt;label&gt;</literal> 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. At the time this keyfile is accessed, the
<literal>neededForBoot</literal> filesystems (see
<literal>fileSystems.&lt;name?&gt;.neededForBoot</literal>)
will have been mounted under <literal>/mnt-root</literal>,
so the keyfile path should usually start with "/mnt-root/".
'';
};
};
};
in
{
options = {
fileSystems = mkOption {
type = with lib.types; attrsOf (submodule encryptedFSOptions);
};
swapDevices = mkOption {
type = with lib.types; listOf (submodule encryptedFSOptions);
};
};
config = mkIf anyEncrypted {
assertions = map (dev: {
assertion = dev.encrypted.label != null;
message = ''
The filesystem for ${dev.mountPoint} has encrypted.enable set to true, but no encrypted.label set
'';
}) encDevs;
boot.initrd = {
luks = {
devices =
builtins.listToAttrs (map (dev: {
name = dev.encrypted.label;
value = { device = dev.encrypted.blkDev; };
}) keylessEncDevs);
forceLuksSupportInInitrd = true;
};
postMountCommands =
concatMapStrings (dev:
"cryptsetup luksOpen --key-file ${dev.encrypted.keyFile} ${dev.encrypted.blkDev} ${dev.encrypted.label};\n"
) keyedEncDevs;
};
};
}

View file

@ -0,0 +1,414 @@
{ config, lib, pkgs, utils, ... }:
with lib;
with utils;
let
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 = {
mountPoint = mkOption {
example = "/mnt/usb";
type = nonEmptyWithoutTrailingSlash;
description = "Location of the mounted the file system.";
};
device = mkOption {
default = null;
example = "/dev/sda";
type = types.nullOr nonEmptyStr;
description = "Location of the device.";
};
fsType = mkOption {
default = "auto";
example = "ext3";
type = nonEmptyStr;
description = "Type of the file system.";
};
options = mkOption {
default = [ "defaults" ];
example = [ "data=journal" ];
description = "Options used to mount the file system.";
type = types.listOf 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</option> and <option>mountPoint</option> 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</option> of some other filesystem.
'';
};
};
config = {
mountPoint = mkDefault name;
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 (if any).";
};
autoFormat = mkOption {
default = false;
type = types.bool;
description = ''
If the device does not currently contain a filesystem (as
determined by <command>blkid</command>, then automatically
format it with the filesystem type specified in
<option>fsType</option>. Use with caution.
'';
};
formatOptions = mkOption {
default = "";
type = types.str;
description = ''
If <option>autoFormat</option> option is set specifies
extra options passed to mkfs.
'';
};
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 = let
defaultFormatOptions =
# -F needed to allow bare block device without partitions
if (builtins.substring 0 3 config.fsType) == "ext" then "-F"
# -q needed for non-interactive operations
else if config.fsType == "jfs" then "-q"
# (same here)
else if config.fsType == "reiserfs" then "-q"
else null;
in {
options = mkIf config.autoResize [ "x-nixos.autoresize" ];
formatOptions = mkIf (defaultFormatOptions != null) (mkDefault defaultFormatOptions);
};
};
# 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);
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]);
description = ''
The file systems to be mounted. It must include an entry for
the root directory (<literal>mountPoint = "/"</literal>). Each
entry in the list is an attribute set with the following fields:
<literal>mountPoint</literal>, <literal>device</literal>,
<literal>fsType</literal> (a file system type recognised by
<command>mount</command>; defaults to
<literal>"auto"</literal>), and <literal>options</literal>
(the mount options passed to <command>mount</command> using the
<option>-o</option> flag; defaults to <literal>[ "defaults" ]</literal>).
Instead of specifying <literal>device</literal>, you can also
specify a volume label (<literal>label</literal>) for file
systems that support it, such as ext2/ext3 (see <command>mke2fs
-L</command>).
'';
};
system.fsPackages = mkOption {
internal = true;
default = [ ];
description = "Packages supplying file system mounters and checkers.";
};
boot.supportedFilesystems = mkOption {
default = [ ];
example = [ "btrfs" ];
type = types.listOf types.str;
description = "Names of supported filesystem types.";
};
boot.specialFileSystems = mkOption {
default = {};
type = types.attrsOf (types.submodule coreFileSystemOpts);
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 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 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 mount(8), tmpfs size option,
for the accepted syntax.
'';
};
};
###### implementation
config = {
assertions = let
ls = sep: concatMapStringsSep sep (x: x.mountPoint);
notAutoResizable = fs: fs.autoResize && !(hasPrefix "ext" fs.fsType || fs.fsType == "f2fs");
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}\"':${if fs.fsType == "auto" then " fsType has to be explicitly set and" else ""} only the ext filesystems and f2fs support it.";
}
];
# 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 = with pkgs; [ fuse3 fuse ] ++ config.system.fsPackages;
environment.etc.fstab.text =
let
fsToSkipCheck = [ "none" "bindfs" "btrfs" "zfs" "tmpfs" "nfs" "vboxsf" "glusterfs" "apfs" ];
skipCheck = fs: fs.noCheck || fs.device == "none" || builtins.elem fs.fsType fsToSkipCheck;
# https://wiki.archlinux.org/index.php/fstab#Filepath_spaces
escape = string: builtins.replaceStrings [ " " "\t" ] [ "\\040" "\\011" ] string;
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.
${concatMapStrings (fs:
(if fs.device != null then escape fs.device
else if fs.label != null then "/dev/disk/by-label/${escape fs.label}"
else throw "No device specified for mount point ${fs.mountPoint}.")
+ " " + escape fs.mountPoint
+ " " + fs.fsType
+ " " + builtins.concatStringsSep "," fs.options
+ " 0"
+ " " + (if skipCheck fs then "0" else
if fs.mountPoint == "/" then "1" else "2")
+ "\n"
) fileSystems}
# Swap devices.
${flip concatMapStrings config.swapDevices (sw:
"${sw.realDevice} none swap ${swapOptions sw}\n"
)}
'';
# Provide a target that pulls in all filesystems.
systemd.targets.fs =
{ description = "All File Systems";
wants = [ "local-fs.target" "remote-fs.target" ];
};
systemd.services =
# Emit systemd services to format requested filesystems.
let
formatDevice = fs:
let
mountPoint' = "${escapeSystemdPath fs.mountPoint}.mount";
device' = escapeSystemdPath fs.device;
device'' = "${device'}.device";
in nameValuePair "mkfs-${device'}"
{ description = "Initialisation of Filesystem ${fs.device}";
wantedBy = [ mountPoint' ];
before = [ mountPoint' "systemd-fsck@${device'}.service" ];
requires = [ device'' ];
after = [ device'' ];
path = [ pkgs.util-linux ] ++ config.system.fsPackages;
script =
''
if ! [ -e "${fs.device}" ]; then exit 1; fi
# FIXME: this is scary. The test could be more robust.
type=$(blkid -p -s TYPE -o value "${fs.device}" || true)
if [ -z "$type" ]; then
echo "creating ${fs.fsType} filesystem on ${fs.device}..."
mkfs.${fs.fsType} ${fs.formatOptions} "${fs.device}"
fi
'';
unitConfig.RequiresMountsFor = [ "${dirOf fs.device}" ];
unitConfig.DefaultDependencies = false; # needed to prevent a cycle
serviceConfig.Type = "oneshot";
};
in listToAttrs (map formatDevice (filter (fs: fs.autoFormat && !(utils.fsNeededForBoot fs)) fileSystems)) // {
# Mount /sys/fs/pstore for evacuating panic logs and crashdumps from persistent storage onto the disk using systemd-pstore.
# This cannot be done with the other special filesystems because the pstore module (which creates the mount point) is not loaded then.
"mount-pstore" = {
serviceConfig = {
Type = "oneshot";
# skip on kernels without the pstore module
ExecCondition = "${pkgs.kmod}/bin/modprobe -b pstore";
ExecStart = pkgs.writeShellScript "mount-pstore.sh" ''
set -eu
# if the pstore module is builtin it will have mounted the persistent store automatically. it may also be already mounted for other reasons.
${pkgs.util-linux}/bin/mountpoint -q /sys/fs/pstore || ${pkgs.util-linux}/bin/mount -t pstore -o nosuid,noexec,nodev pstore /sys/fs/pstore
# wait up to 1.5 seconds for the backend to be registered and the files to appear. a systemd path unit cannot detect this happening; and succeeding after a restart would not start dependent units.
TRIES=15
while [ "$(cat /sys/module/pstore/parameters/backend)" = "(null)" ]; do
if (( $TRIES )); then
sleep 0.1
TRIES=$((TRIES-1))
else
echo "Persistent Storage backend was not registered in time." >&2
break
fi
done
'';
RemainAfterExit = true;
};
unitConfig = {
ConditionVirtualization = "!container";
DefaultDependencies = false; # needed to prevent a cycle
};
before = [ "systemd-pstore.service" ];
wantedBy = [ "systemd-pstore.service" ];
};
};
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 = {
"/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}" ]; };
# 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" ]; };
};
};
}

View file

@ -0,0 +1,22 @@
{ config, lib, pkgs, ... }:
with lib;
let
inInitrd = any (fs: fs == "apfs") config.boot.initrd.supportedFilesystems;
in
{
config = mkIf (any (fs: fs == "apfs") config.boot.supportedFilesystems) {
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,65 @@
{ config, lib, pkgs, utils, ... }:
with lib;
let
bootFs = 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"
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 sucessfully unlocked
printf "unlocking failed!\n"
prompt $name
done
printf "unlocking successful.\n"
fi
}
'';
openCommand = name: fs:
let
# 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 = head (splitString ":" fs.device);
in
''
tryUnlock ${name} ${firstDevice}
'';
in
{
config = mkIf (elem "bcachefs" config.boot.supportedFilesystems) (mkMerge [
{
system.fsPackages = [ pkgs.bcachefs-tools ];
# use kernel package with bcachefs support until it's in mainline
boot.kernelPackages = pkgs.linuxPackages_testing_bcachefs;
}
(mkIf ((elem "bcachefs" config.boot.initrd.supportedFilesystems) || (bootFs != {})) {
# chacha20 and poly1305 are required only for decryption attempts
boot.initrd.availableKernelModules = [ "bcachefs" "sha256" "chacha20" "poly1305" ];
boot.initrd.extraUtilsCommands = ''
copy_bin_and_libs ${pkgs.bcachefs-tools}/bin/bcachefs
'';
boot.initrd.extraUtilsCommandsTest = ''
$out/bin/bcachefs version
'';
boot.initrd.postDeviceCommands = commonFunctions + concatStrings (mapAttrsToList openCommand bootFs);
})
]);
}

View file

@ -0,0 +1,149 @@
{ config, lib, pkgs, utils, ... }:
with lib;
let
inInitrd = any (fs: fs == "btrfs") config.boot.initrd.supportedFilesystems;
inSystem = any (fs: fs == "btrfs") config.boot.supportedFilesystems;
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 regularily call <command>btrfs scrub</command> on.
Defaults to all mount points with btrfs filesystems.
If you mount a filesystem multiple times or additionally mount subvolumes,
you need to manually specify this list to avoid scrubbing 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
(<citerefentry><refentrytitle>btrfs-scrub</refentrytitle>
<manvolnum>8</manvolnum></citerefentry>).
See
<citerefentry><refentrytitle>systemd.time</refentrytitle>
<manvolnum>7</manvolnum></citerefentry>
for more information on the syntax.
'';
};
};
};
config = mkMerge [
(mkIf enableBtrfs {
system.fsPackages = [ pkgs.btrfs-progs ];
boot.initrd.kernelModules = mkIf inInitrd [ "btrfs" ];
boot.initrd.availableKernelModules = mkIf inInitrd (
[ "crc32c" ]
++ optionals (config.boot.kernelPackages.kernel.kernelAtLeast "5.5") [
# Needed for mounting filesystems with new checksums
"xxhash_generic"
"blake2b_generic"
"sha256_generic" # Should be baked into our kernel, just to be sure
]
);
boot.initrd.extraUtilsCommands = mkIf (inInitrd && !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 (inInitrd && !config.boot.initrd.systemd.enable)
''
$out/bin/btrfs --version
'';
boot.initrd.postDeviceCommands = mkIf (inInitrd && !config.boot.initrd.systemd.enable)
''
btrfs device scan
'';
})
(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 yield duplicated units if the user mounts a filesystem multiple times
# or additionally mounts subvolumes, but going the other way around via devices would
# yield duplicated units when a filesystem spans multiple devices.
# This way around seems like the more sensible default.
services.btrfs.autoScrub.fileSystems = mkDefault (mapAttrsToList (name: fs: fs.mountPoint)
(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}";
# 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,25 @@
{ config, lib, pkgs, ... }:
with lib;
let
inInitrd = any (fs: fs == "cifs") config.boot.initrd.supportedFilesystems;
in
{
config = {
system.fsPackages = mkIf (any (fs: fs == "cifs") config.boot.supportedFilesystems) [ 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
'';
};
}

View file

@ -0,0 +1,24 @@
{ config, lib, pkgs, ... }:
# TODO: make ecryptfs work in initramfs?
with lib;
{
config = mkIf (any (fs: fs == "ecryptfs") config.boot.supportedFilesystems) {
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,13 @@
{ config, lib, pkgs, ... }:
with lib;
{
config = mkIf (any (fs: fs == "exfat") config.boot.supportedFilesystems) {
system.fsPackages = if config.boot.kernelPackages.kernelOlder "5.7" then [
pkgs.exfat # FUSE
] else [
pkgs.exfatprogs # non-FUSE
];
};
}

View file

@ -0,0 +1,28 @@
{ config, lib, pkgs, ... }:
let
inInitrd = lib.any (fs: fs == "ext2" || fs == "ext3" || fs == "ext4") config.boot.initrd.supportedFilesystems;
in
{
config = {
system.fsPackages = lib.mkIf (config.boot.initrd.systemd.enable -> inInitrd) [ 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
'';
};
}

View file

@ -0,0 +1,25 @@
{ config, pkgs, lib, ... }:
with lib;
let
inInitrd = any (fs: fs == "f2fs") config.boot.initrd.supportedFilesystems;
fileSystems = filter (x: x.fsType == "f2fs") config.system.build.fileSystems;
in
{
config = mkIf (any (fs: fs == "f2fs") config.boot.supportedFilesystems) {
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
${optionalString (any (fs: fs.autoResize) fileSystems) ''
# We need f2fs-tools' tools to resize filesystems
copy_bin_and_libs ${pkgs.f2fs-tools}/sbin/resize.f2fs
''}
'';
};
}

View file

@ -0,0 +1,11 @@
{ config, lib, pkgs, ... }:
with lib;
{
config = mkIf (any (fs: fs == "glusterfs") config.boot.supportedFilesystems) {
system.fsPackages = [ pkgs.glusterfs ];
};
}

View file

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

View file

@ -0,0 +1,135 @@
{ config, lib, pkgs, ... }:
with lib;
let
inInitrd = any (fs: fs == "nfs") config.boot.initrd.supportedFilesystems;
nfsStateDir = "/var/lib/nfs";
rpcMountpoint = "${nfsStateDir}/rpc_pipefs";
format = pkgs.formats.ini {};
idmapdConfFile = format.generate "idmapd.conf" cfg.idmapd.settings;
nfsConfFile = pkgs.writeText "nfs.conf" cfg.extraConfig;
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 = mkOption {
type = format.type;
default = {};
description = ''
libnfsidmap configuration. Refer to
<link xlink:href="https://linux.die.net/man/5/idmapd.conf"/>
for details.
'';
example = literalExpression ''
{
Translation = {
GSS-Methods = "static,nsswitch";
};
Static = {
"root/hostname.domain.com@REALM.COM" = "root";
};
}
'';
};
extraConfig = mkOption {
type = types.lines;
default = "";
description = ''
Extra nfs-utils configuration.
'';
};
};
};
###### implementation
config = mkIf (any (fs: fs == "nfs" || fs == "nfs4") config.boot.supportedFilesystems) {
services.rpcbind.enable = true;
services.nfs.idmapd.settings = {
General = mkMerge [
{ Pipefs-Directory = rpcMountpoint; }
(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 = 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 = mkDefault false;
};
systemd.services.nfs-server =
{ restartTriggers = [ nfsConfFile ];
enable = 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,11 @@
{ config, lib, pkgs, ... }:
with lib;
{
config = mkIf (any (fs: fs == "ntfs" || fs == "ntfs-3g") config.boot.supportedFilesystems) {
system.fsPackages = [ pkgs.ntfs3g ];
};
}

View file

@ -0,0 +1,25 @@
{ config, lib, pkgs, ... }:
with lib;
let
inInitrd = any (fs: fs == "reiserfs") config.boot.initrd.supportedFilesystems;
in
{
config = mkIf (any (fs: fs == "reiserfs") config.boot.supportedFilesystems) {
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
'';
};
}

View file

@ -0,0 +1,43 @@
{ config, pkgs, lib, ... }:
{
config = lib.mkMerge [
(lib.mkIf (lib.any (fs: fs == "unionfs-fuse") config.boot.initrd.supportedFilesystems) {
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 (lib.any (fs: fs == "unionfs-fuse") config.boot.supportedFilesystems) {
system.fsPackages = [ pkgs.unionfs-fuse ];
})
];
}

View file

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

View file

@ -0,0 +1,25 @@
{ config, lib, pkgs, ... }:
with lib;
let
inInitrd = any (fs: fs == "vfat") config.boot.initrd.supportedFilesystems;
in
{
config = mkIf (any (fs: fs == "vfat") config.boot.supportedFilesystems) {
system.fsPackages = [ pkgs.dosfstools ];
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
'';
};
}

View file

@ -0,0 +1,30 @@
{ config, lib, pkgs, ... }:
with lib;
let
inInitrd = any (fs: fs == "xfs") config.boot.initrd.supportedFilesystems;
in
{
config = mkIf (any (fs: fs == "xfs") config.boot.supportedFilesystems) {
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
'';
};
}

View file

@ -0,0 +1,806 @@
{ config, lib, options, pkgs, utils, ... }:
#
# TODO: zfs tunables
with utils;
with lib;
let
cfgZfs = config.boot.zfs;
optZfs = options.boot.zfs;
cfgExpandOnBoot = config.services.zfs.expandOnBoot;
cfgSnapshots = config.services.zfs.autoSnapshot;
cfgSnapFlags = cfgSnapshots.flags;
cfgScrub = config.services.zfs.autoScrub;
cfgTrim = config.services.zfs.trim;
cfgZED = config.services.zfs.zed;
inInitrd = any (fs: fs == "zfs") config.boot.initrd.supportedFilesystems;
inSystem = any (fs: fs == "zfs") config.boot.supportedFilesystems;
autosnapPkg = pkgs.zfstools.override {
zfs = cfgZfs.package;
};
zfsAutoSnap = "${autosnapPkg}/bin/zfs-auto-snapshot";
datasetToPool = x: elemAt (splitString "/" x) 0;
fsToPool = fs: datasetToPool fs.device;
zfsFilesystems = filter (x: x.fsType == "zfs") config.system.build.fileSystems;
allPools = unique ((map fsToPool zfsFilesystems) ++ cfgZfs.extraPools);
rootPools = unique (map fsToPool (filter fsNeededForBoot zfsFilesystems));
dataPools = unique (filter (pool: !(elem pool rootPools)) allPools);
snapshotNames = [ "frequent" "hourly" "daily" "weekly" "monthly" ];
# When importing ZFS pools, there's one difficulty: These scripts may run
# before the backing devices (physical HDDs, etc.) of the pool have been
# scanned and initialized.
#
# An attempted import with all devices missing will just fail, and can be
# retried, but an import where e.g. two out of three disks in a three-way
# mirror are missing, will succeed. This is a problem: When the missing disks
# are later discovered, they won't be automatically set online, rendering the
# pool redundancy-less (and far slower) until such time as the system reboots.
#
# The solution is the below. poolReady checks the status of an un-imported
# pool, to see if *every* device is available -- in which case the pool will be
# in state ONLINE, as opposed to DEGRADED, FAULTED or MISSING.
#
# The import scripts then loop over this, waiting until the pool is ready or a
# sufficient amount of time has passed that we can assume it won't be. In the
# latter case it makes one last attempt at importing, allowing the system to
# (eventually) boot even with a degraded pool.
importLib = {zpoolCmd, awkCmd, cfgZfs}: ''
for o in $(cat /proc/cmdline); do
case $o in
zfs_force|zfs_force=1|zfs_force=y)
ZFS_FORCE="-f"
;;
esac
done
poolReady() {
pool="$1"
state="$("${zpoolCmd}" import 2>/dev/null | "${awkCmd}" "/pool: $pool/ { found = 1 }; /state:/ { if (found == 1) { print \$2; exit } }; END { if (found == 0) { print \"MISSING\" } }")"
if [[ "$state" = "ONLINE" ]]; then
return 0
else
echo "Pool $pool in state $state, waiting"
return 1
fi
}
poolImported() {
pool="$1"
"${zpoolCmd}" list "$pool" >/dev/null 2>/dev/null
}
poolImport() {
pool="$1"
"${zpoolCmd}" import -d "${cfgZfs.devNodes}" -N $ZFS_FORCE "$pool"
}
'';
getPoolFilesystems = pool:
filter (x: x.fsType == "zfs" && (fsToPool x) == pool) config.system.build.fileSystems;
getPoolMounts = prefix: pool:
let
# Remove the "/" suffix because even though most mountpoints
# won't have it, the "/" mountpoint will, and we can't have the
# trailing slash in "/sysroot/" in stage 1.
mountPoint = fs: escapeSystemdPath (prefix + (lib.removeSuffix "/" fs.mountPoint));
in
map (x: "${mountPoint x}.mount") (getPoolFilesystems pool);
getKeyLocations = pool:
if isBool cfgZfs.requestEncryptionCredentials
then "${cfgZfs.package}/sbin/zfs list -rHo name,keylocation,keystatus ${pool}"
else "${cfgZfs.package}/sbin/zfs list -Ho name,keylocation,keystatus ${toString (filter (x: datasetToPool x == pool) cfgZfs.requestEncryptionCredentials)}";
createImportService = { pool, systemd, force, prefix ? "" }:
nameValuePair "zfs-import-${pool}" {
description = "Import ZFS pool \"${pool}\"";
# we need systemd-udev-settle to ensure devices are available
# In the future, hopefully someone will complete this:
# https://github.com/zfsonlinux/zfs/pull/4943
requires = [ "systemd-udev-settle.service" ];
after = [
"systemd-udev-settle.service"
"systemd-modules-load.service"
"systemd-ask-password-console.service"
];
wantedBy = (getPoolMounts prefix pool) ++ [ "local-fs.target" ];
before = (getPoolMounts prefix pool) ++ [ "local-fs.target" ];
unitConfig = {
DefaultDependencies = "no";
};
serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
};
environment.ZFS_FORCE = optionalString force "-f";
script = (importLib {
# See comments at importLib definition.
zpoolCmd = "${cfgZfs.package}/sbin/zpool";
awkCmd = "${pkgs.gawk}/bin/awk";
inherit cfgZfs;
}) + ''
poolImported "${pool}" && exit
echo -n "importing ZFS pool \"${pool}\"..."
# Loop across the import until it succeeds, because the devices needed may not be discovered yet.
for trial in `seq 1 60`; do
poolReady "${pool}" && poolImport "${pool}" && break
sleep 1
done
poolImported "${pool}" || poolImport "${pool}" # Try one last time, e.g. to import a degraded pool.
if poolImported "${pool}"; then
${optionalString (if isBool cfgZfs.requestEncryptionCredentials
then cfgZfs.requestEncryptionCredentials
else cfgZfs.requestEncryptionCredentials != []) ''
${getKeyLocations pool} | while IFS=$'\t' read ds kl ks; do
{
if [[ "$ks" != unavailable ]]; then
continue
fi
case "$kl" in
none )
;;
prompt )
tries=3
success=false
while [[ $success != true ]] && [[ $tries -gt 0 ]]; do
${systemd}/bin/systemd-ask-password "Enter key for $ds:" | ${cfgZfs.package}/sbin/zfs load-key "$ds" \
&& success=true \
|| tries=$((tries - 1))
done
[[ $success = true ]]
;;
* )
${cfgZfs.package}/sbin/zfs load-key "$ds"
;;
esac
} < /dev/null # To protect while read ds kl in case anything reads stdin
done
''}
echo "Successfully imported ${pool}"
else
exit 1
fi
'';
};
zedConf = generators.toKeyValue {
mkKeyValue = generators.mkKeyValueDefault {
mkValueString = v:
if isInt v then toString v
else if isString v then "\"${v}\""
else if true == v then "1"
else if false == v then "0"
else if isList v then "\"" + (concatStringsSep " " v) + "\""
else err "this value is" (toString v);
} "=";
} cfgZED.settings;
in
{
imports = [
(mkRemovedOptionModule [ "boot" "zfs" "enableLegacyCrypto" ] "The corresponding package was removed from nixpkgs.")
];
###### interface
options = {
boot.zfs = {
package = mkOption {
readOnly = true;
type = types.package;
default = if config.boot.zfs.enableUnstable then pkgs.zfsUnstable else pkgs.zfs;
defaultText = literalExpression "if config.boot.zfs.enableUnstable then pkgs.zfsUnstable else pkgs.zfs";
description = "Configured ZFS userland tools package.";
};
enabled = mkOption {
readOnly = true;
type = types.bool;
default = inInitrd || inSystem;
defaultText = literalDocBook "<literal>true</literal> if ZFS filesystem support is enabled";
description = "True if ZFS filesystem support is enabled";
};
enableUnstable = mkOption {
type = types.bool;
default = false;
description = ''
Use the unstable zfs package. This might be an option, if the latest
kernel is not yet supported by a published release of ZFS. Enabling
this option will install a development version of ZFS on Linux. The
version will have already passed an extensive test suite, but it is
more likely to hit an undiscovered bug compared to running a released
version of ZFS on Linux.
'';
};
extraPools = mkOption {
type = types.listOf types.str;
default = [];
example = [ "tank" "data" ];
description = ''
Name or GUID of extra ZFS pools that you wish to import during boot.
Usually this is not necessary. Instead, you should set the mountpoint property
of ZFS filesystems to <literal>legacy</literal> and add the ZFS filesystems to
NixOS's <option>fileSystems</option> option, which makes NixOS automatically
import the associated pool.
However, in some cases (e.g. if you have many filesystems) it may be preferable
to exclusively use ZFS commands to manage filesystems. If so, since NixOS/systemd
will not be managing those filesystems, you will need to specify the ZFS pool here
so that NixOS automatically imports it on every boot.
'';
};
devNodes = mkOption {
type = types.path;
default = "/dev/disk/by-id";
description = ''
Name of directory from which to import ZFS devices.
This should be a path under /dev containing stable names for all devices needed, as
import may fail if device nodes are renamed concurrently with a device failing.
'';
};
forceImportRoot = mkOption {
type = types.bool;
default = true;
description = ''
Forcibly import the ZFS root pool(s) during early boot.
This is enabled by default for backwards compatibility purposes, but it is highly
recommended to disable this option, as it bypasses some of the safeguards ZFS uses
to protect your ZFS pools.
If you set this option to <literal>false</literal> and NixOS subsequently fails to
boot because it cannot import the root pool, you should boot with the
<literal>zfs_force=1</literal> option as a kernel parameter (e.g. by manually
editing the kernel params in grub during boot). You should only need to do this
once.
'';
};
forceImportAll = mkOption {
type = types.bool;
default = false;
description = ''
Forcibly import all ZFS pool(s).
If you set this option to <literal>false</literal> and NixOS subsequently fails to
import your non-root ZFS pool(s), you should manually import each pool with
"zpool import -f &lt;pool-name&gt;", and then reboot. You should only need to do
this once.
'';
};
requestEncryptionCredentials = mkOption {
type = types.either types.bool (types.listOf types.str);
default = true;
example = [ "tank" "data" ];
description = ''
If true on import encryption keys or passwords for all encrypted datasets
are requested. To only decrypt selected datasets supply a list of dataset
names instead. For root pools the encryption key can be supplied via both
an interactive prompt (keylocation=prompt) and from a file (keylocation=file://).
'';
};
};
services.zfs.autoSnapshot = {
enable = mkOption {
default = false;
type = types.bool;
description = ''
Enable the (OpenSolaris-compatible) ZFS auto-snapshotting service.
Note that you must set the <literal>com.sun:auto-snapshot</literal>
property to <literal>true</literal> on all datasets which you wish
to auto-snapshot.
You can override a child dataset to use, or not use auto-snapshotting
by setting its flag with the given interval:
<literal>zfs set com.sun:auto-snapshot:weekly=false DATASET</literal>
'';
};
flags = mkOption {
default = "-k -p";
example = "-k -p --utc";
type = types.str;
description = ''
Flags to pass to the zfs-auto-snapshot command.
Run <literal>zfs-auto-snapshot</literal> (without any arguments) to
see available flags.
If it's not too inconvenient for snapshots to have timestamps in UTC,
it is suggested that you append <literal>--utc</literal> to the list
of default options (see example).
Otherwise, snapshot names can cause name conflicts or apparent time
reversals due to daylight savings, timezone or other date/time changes.
'';
};
frequent = mkOption {
default = 4;
type = types.int;
description = ''
Number of frequent (15-minute) auto-snapshots that you wish to keep.
'';
};
hourly = mkOption {
default = 24;
type = types.int;
description = ''
Number of hourly auto-snapshots that you wish to keep.
'';
};
daily = mkOption {
default = 7;
type = types.int;
description = ''
Number of daily auto-snapshots that you wish to keep.
'';
};
weekly = mkOption {
default = 4;
type = types.int;
description = ''
Number of weekly auto-snapshots that you wish to keep.
'';
};
monthly = mkOption {
default = 12;
type = types.int;
description = ''
Number of monthly auto-snapshots that you wish to keep.
'';
};
};
services.zfs.trim = {
enable = mkOption {
description = "Whether to enable periodic TRIM on all ZFS pools.";
default = true;
example = false;
type = types.bool;
};
interval = mkOption {
default = "weekly";
type = types.str;
example = "daily";
description = ''
How often we run trim. For most desktop and server systems
a sufficient trimming frequency is once a week.
The format is described in
<citerefentry><refentrytitle>systemd.time</refentrytitle>
<manvolnum>7</manvolnum></citerefentry>.
'';
};
};
services.zfs.autoScrub = {
enable = mkEnableOption "periodic scrubbing of ZFS pools";
interval = mkOption {
default = "Sun, 02:00";
type = types.str;
example = "daily";
description = ''
Systemd calendar expression when to scrub ZFS pools. See
<citerefentry><refentrytitle>systemd.time</refentrytitle>
<manvolnum>7</manvolnum></citerefentry>.
'';
};
pools = mkOption {
default = [];
type = types.listOf types.str;
example = [ "tank" ];
description = ''
List of ZFS pools to periodically scrub. If empty, all pools
will be scrubbed.
'';
};
};
services.zfs.expandOnBoot = mkOption {
type = types.either (types.enum [ "disabled" "all" ]) (types.listOf types.str);
default = "disabled";
example = [ "tank" "dozer" ];
description = ''
After importing, expand each device in the specified pools.
Set the value to the plain string "all" to expand all pools on boot:
services.zfs.expandOnBoot = "all";
or set the value to a list of pools to expand the disks of specific pools:
services.zfs.expandOnBoot = [ "tank" "dozer" ];
'';
};
services.zfs.zed = {
enableMail = mkEnableOption "ZED's ability to send emails" // {
default = cfgZfs.package.enableMail;
defaultText = literalExpression "config.${optZfs.package}.enableMail";
};
settings = mkOption {
type = with types; attrsOf (oneOf [ str int bool (listOf str) ]);
example = literalExpression ''
{
ZED_DEBUG_LOG = "/tmp/zed.debug.log";
ZED_EMAIL_ADDR = [ "root" ];
ZED_EMAIL_PROG = "mail";
ZED_EMAIL_OPTS = "-s '@SUBJECT@' @ADDRESS@";
ZED_NOTIFY_INTERVAL_SECS = 3600;
ZED_NOTIFY_VERBOSE = false;
ZED_USE_ENCLOSURE_LEDS = true;
ZED_SCRUB_AFTER_RESILVER = false;
}
'';
description = ''
ZFS Event Daemon /etc/zfs/zed.d/zed.rc content
See
<citerefentry><refentrytitle>zed</refentrytitle><manvolnum>8</manvolnum></citerefentry>
for details on ZED and the scripts in /etc/zfs/zed.d to find the possible variables
'';
};
};
};
###### implementation
config = mkMerge [
(mkIf cfgZfs.enabled {
assertions = [
{
assertion = cfgZED.enableMail -> cfgZfs.package.enableMail;
message = ''
To allow ZED to send emails, ZFS needs to be configured to enable
this. To do so, one must override the `zfs` package and set
`enableMail` to true.
'';
}
{
assertion = config.networking.hostId != null;
message = "ZFS requires networking.hostId to be set";
}
{
assertion = !cfgZfs.forceImportAll || cfgZfs.forceImportRoot;
message = "If you enable boot.zfs.forceImportAll, you must also enable boot.zfs.forceImportRoot";
}
];
boot = {
kernelModules = [ "zfs" ];
extraModulePackages = [
(if config.boot.zfs.enableUnstable then
config.boot.kernelPackages.zfsUnstable
else
config.boot.kernelPackages.zfs)
];
};
boot.initrd = mkIf inInitrd {
kernelModules = [ "zfs" ] ++ optional (!cfgZfs.enableUnstable) "spl";
extraUtilsCommands =
''
copy_bin_and_libs ${cfgZfs.package}/sbin/zfs
copy_bin_and_libs ${cfgZfs.package}/sbin/zdb
copy_bin_and_libs ${cfgZfs.package}/sbin/zpool
'';
extraUtilsCommandsTest = mkIf inInitrd
''
$out/bin/zfs --help >/dev/null 2>&1
$out/bin/zpool --help >/dev/null 2>&1
'';
postDeviceCommands = concatStringsSep "\n" ([''
ZFS_FORCE="${optionalString cfgZfs.forceImportRoot "-f"}"
''] ++ [(importLib {
# See comments at importLib definition.
zpoolCmd = "zpool";
awkCmd = "awk";
inherit cfgZfs;
})] ++ (map (pool: ''
echo -n "importing root ZFS pool \"${pool}\"..."
# Loop across the import until it succeeds, because the devices needed may not be discovered yet.
if ! poolImported "${pool}"; then
for trial in `seq 1 60`; do
poolReady "${pool}" > /dev/null && msg="$(poolImport "${pool}" 2>&1)" && break
sleep 1
echo -n .
done
echo
if [[ -n "$msg" ]]; then
echo "$msg";
fi
poolImported "${pool}" || poolImport "${pool}" # Try one last time, e.g. to import a degraded pool.
fi
${if isBool cfgZfs.requestEncryptionCredentials
then optionalString cfgZfs.requestEncryptionCredentials ''
zfs load-key -a
''
else concatMapStrings (fs: ''
zfs load-key ${fs}
'') cfgZfs.requestEncryptionCredentials}
'') rootPools));
# Systemd in stage 1
systemd = {
packages = [cfgZfs.package];
services = listToAttrs (map (pool: createImportService {
inherit pool;
systemd = config.boot.initrd.systemd.package;
force = cfgZfs.forceImportRoot;
prefix = "/sysroot";
}) rootPools);
extraBin = {
# zpool and zfs are already in thanks to fsPackages
awk = "${pkgs.gawk}/bin/awk";
};
};
};
systemd.shutdownRamfs.contents."/etc/systemd/system-shutdown/zpool".source = pkgs.writeShellScript "zpool-sync-shutdown" ''
exec ${cfgZfs.package}/bin/zpool sync
'';
systemd.shutdownRamfs.storePaths = ["${cfgZfs.package}/bin/zpool"];
# TODO FIXME See https://github.com/NixOS/nixpkgs/pull/99386#issuecomment-798813567. To not break people's bootloader and as probably not everybody would read release notes that thoroughly add inSystem.
boot.loader.grub = mkIf (inInitrd || inSystem) {
zfsSupport = true;
};
services.zfs.zed.settings = {
ZED_EMAIL_PROG = mkIf cfgZED.enableMail (mkDefault "${pkgs.mailutils}/bin/mail");
PATH = lib.makeBinPath [
cfgZfs.package
pkgs.coreutils
pkgs.curl
pkgs.gawk
pkgs.gnugrep
pkgs.gnused
pkgs.nettools
pkgs.util-linux
];
};
environment.etc = genAttrs
(map
(file: "zfs/zed.d/${file}")
[
"all-syslog.sh"
"pool_import-led.sh"
"resilver_finish-start-scrub.sh"
"statechange-led.sh"
"vdev_attach-led.sh"
"zed-functions.sh"
"data-notify.sh"
"resilver_finish-notify.sh"
"scrub_finish-notify.sh"
"statechange-notify.sh"
"vdev_clear-led.sh"
]
)
(file: { source = "${cfgZfs.package}/etc/${file}"; })
// {
"zfs/zed.d/zed.rc".text = zedConf;
"zfs/zpool.d".source = "${cfgZfs.package}/etc/zfs/zpool.d/";
};
system.fsPackages = [ cfgZfs.package ]; # XXX: needed? zfs doesn't have (need) a fsck
environment.systemPackages = [ cfgZfs.package ]
++ optional cfgSnapshots.enable autosnapPkg; # so the user can run the command to see flags
services.udev.packages = [ cfgZfs.package ]; # to hook zvol naming, etc.
systemd.packages = [ cfgZfs.package ];
systemd.services = let
createImportService' = pool: createImportService {
inherit pool;
systemd = config.systemd.package;
force = cfgZfs.forceImportAll;
};
# This forces a sync of any ZFS pools prior to poweroff, even if they're set
# to sync=disabled.
createSyncService = pool:
nameValuePair "zfs-sync-${pool}" {
description = "Sync ZFS pool \"${pool}\"";
wantedBy = [ "shutdown.target" ];
unitConfig = {
DefaultDependencies = false;
};
serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
};
script = ''
${cfgZfs.package}/sbin/zfs set nixos:shutdown-time="$(date)" "${pool}"
'';
};
createZfsService = serv:
nameValuePair serv {
after = [ "systemd-modules-load.service" ];
wantedBy = [ "zfs.target" ];
};
in listToAttrs (map createImportService' dataPools ++
map createSyncService allPools ++
map createZfsService [ "zfs-mount" "zfs-share" "zfs-zed" ]);
systemd.targets.zfs-import =
let
services = map (pool: "zfs-import-${pool}.service") dataPools;
in
{
requires = services;
after = services;
wantedBy = [ "zfs.target" ];
};
systemd.targets.zfs.wantedBy = [ "multi-user.target" ];
})
(mkIf (cfgZfs.enabled && cfgExpandOnBoot != "disabled") {
systemd.services."zpool-expand@" = {
description = "Expand ZFS pools";
after = [ "zfs.target" ];
serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
};
scriptArgs = "%i";
path = [ cfgZfs.package ];
script = ''
pool=$1
echo "Expanding all devices for $pool."
${pkgs.zpool-auto-expand-partitions}/bin/zpool_part_disks --automatically-grow "$pool"
'';
};
systemd.services."zpool-expand-pools" =
let
# Create a string, to be interpolated in a bash script
# which enumerates all of the pools to expand.
# If the `pools` option is `true`, we want to dynamically
# expand every pool. Otherwise we want to enumerate
# just the specifically provided list of pools.
poolListProvider = if cfgExpandOnBoot == "all"
then "$(zpool list -H | awk '{print $1}')"
else lib.escapeShellArgs cfgExpandOnBoot;
in
{
description = "Expand specified ZFS pools";
wantedBy = [ "default.target" ];
after = [ "zfs.target" ];
serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
};
script = ''
for pool in ${poolListProvider}; do
systemctl start --no-block "zpool-expand@$pool"
done
'';
};
})
(mkIf (cfgZfs.enabled && cfgSnapshots.enable) {
systemd.services = let
descr = name: if name == "frequent" then "15 mins"
else if name == "hourly" then "hour"
else if name == "daily" then "day"
else if name == "weekly" then "week"
else if name == "monthly" then "month"
else throw "unknown snapshot name";
numSnapshots = name: builtins.getAttr name cfgSnapshots;
in builtins.listToAttrs (map (snapName:
{
name = "zfs-snapshot-${snapName}";
value = {
description = "ZFS auto-snapshotting every ${descr snapName}";
after = [ "zfs-import.target" ];
serviceConfig = {
Type = "oneshot";
ExecStart = "${zfsAutoSnap} ${cfgSnapFlags} ${snapName} ${toString (numSnapshots snapName)}";
};
restartIfChanged = false;
};
}) snapshotNames);
systemd.timers = let
timer = name: if name == "frequent" then "*:0,15,30,45" else name;
in builtins.listToAttrs (map (snapName:
{
name = "zfs-snapshot-${snapName}";
value = {
wantedBy = [ "timers.target" ];
timerConfig = {
OnCalendar = timer snapName;
Persistent = "yes";
};
};
}) snapshotNames);
})
(mkIf (cfgZfs.enabled && cfgScrub.enable) {
systemd.services.zfs-scrub = {
description = "ZFS pools scrubbing";
after = [ "zfs-import.target" ];
serviceConfig = {
Type = "oneshot";
};
script = ''
${cfgZfs.package}/bin/zpool scrub ${
if cfgScrub.pools != [] then
(concatStringsSep " " cfgScrub.pools)
else
"$(${cfgZfs.package}/bin/zpool list -H -o name)"
}
'';
};
systemd.timers.zfs-scrub = {
wantedBy = [ "timers.target" ];
after = [ "multi-user.target" ]; # Apparently scrubbing before boot is complete hangs the system? #53583
timerConfig = {
OnCalendar = cfgScrub.interval;
Persistent = "yes";
};
};
})
(mkIf (cfgZfs.enabled && cfgTrim.enable) {
systemd.services.zpool-trim = {
description = "ZFS pools trim";
after = [ "zfs-import.target" ];
path = [ cfgZfs.package ];
startAt = cfgTrim.interval;
# By default we ignore errors returned by the trim command, in case:
# - HDDs are mixed with SSDs
# - There is a SSDs in a pool that is currently trimmed.
# - There are only HDDs and we would set the system in a degraded state
serviceConfig.ExecStart = "${pkgs.runtimeShell} -c 'for pool in $(zpool list -H -o name); do zpool trim $pool; done || true' ";
};
systemd.timers.zpool-trim.timerConfig.Persistent = "yes";
})
];
}

133
nixos/modules/tasks/lvm.nix Normal file
View file

@ -0,0 +1,133 @@
{ config, lib, pkgs, ... }:
with lib;
let
cfg = config.services.lvm;
in {
options.services.lvm = {
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 "enable booting from LVM2 in the initrd") // {
visible = false;
};
config = mkMerge [
({
# minimal configuration file to make lvmconfig/lvm2-activation-generator happy
environment.etc."lvm/lvm.conf".text = "config {}";
})
(mkIf (!config.boot.isContainer) {
systemd.tmpfiles.packages = [ cfg.package.out ];
environment.systemPackages = [ cfg.package ];
systemd.packages = [ cfg.package ];
services.udev.packages = [ cfg.package.out ];
# We need lvm2 for the device-mapper rules
boot.initrd.services.udev.packages = lib.mkIf config.boot.initrd.services.lvm.enable [ cfg.package ];
# The device-mapper rules want to call tools from lvm2
boot.initrd.systemd.initrdBin = lib.mkIf config.boot.initrd.services.lvm.enable [ cfg.package ];
boot.initrd.services.udev.binPackages = lib.mkIf config.boot.initrd.services.lvm.enable [ 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 {
boot = {
initrd = {
kernelModules = [ "kvdo" ];
systemd.initrdBin = lib.mkIf config.boot.initrd.services.lvm.enable [ pkgs.vdo ];
extraUtilsCommands = mkIf (!config.boot.initrd.systemd.enable)''
ls ${pkgs.vdo}/bin/ | grep -v adaptLVMVDO | while read BIN; do
copy_bin_and_libs ${pkgs.vdo}/bin/$BIN
done
'';
extraUtilsCommandsTest = mkIf (!config.boot.initrd.systemd.enable)''
ls ${pkgs.vdo}/bin/ | grep -v adaptLVMVDO | while read BIN; do
$out/bin/$(basename $BIN) --help > /dev/null
done
'';
};
extraModulePackages = [ config.boot.kernelPackages.kvdo ];
};
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,628 @@
{ 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 "$I" nomaster
done
[ "$UPDATED" -eq "1" ] && break
done
ip link set "${i}" down 2>/dev/null || true
ip link del "${i}" 2>/dev/null || true
'';
# 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;
} // optionalAttrs (i.wakeOnLan.enable == true) {
WakeOnLan = "magic";
};
};
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 (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.vlans) ||
(hasAttr dev cfg.vswitches)
then [ "${dev}-netdev.service" ]
else optional (dev != null && dev != "lo" && !config.boot.isContainer) (subsystemDevice dev);
hasDefaultGatewaySet = (cfg.defaultGateway != null && cfg.defaultGateway.address != "")
|| (cfg.enableIPv6 && cfg.defaultGateway6 != null && cfg.defaultGateway6.address != "");
networkLocalCommands = {
after = [ "network-setup.service" ];
bindsTo = [ "network-setup.service" ];
};
networkSetup =
{ description = "Networking Setup";
after = [ "network-pre.target" "systemd-udevd.service" "systemd-sysctl.service" ];
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.
${optionalString (cfg.defaultGateway != null && cfg.defaultGateway.address != "") ''
${optionalString (cfg.defaultGateway.interface != null) ''
ip route replace ${cfg.defaultGateway.address} dev ${cfg.defaultGateway.interface} ${optionalString (cfg.defaultGateway.metric != null)
"metric ${toString cfg.defaultGateway.metric}"
} proto static
''}
ip route replace default ${optionalString (cfg.defaultGateway.metric != null)
"metric ${toString cfg.defaultGateway.metric}"
} via "${cfg.defaultGateway.address}" ${
optionalString (cfg.defaultGatewayWindowSize != null)
"window ${toString cfg.defaultGatewayWindowSize}"} ${
optionalString (cfg.defaultGateway.interface != null)
"dev ${cfg.defaultGateway.interface}"} proto static
''}
${optionalString (cfg.defaultGateway6 != null && cfg.defaultGateway6.address != "") ''
${optionalString (cfg.defaultGateway6.interface != null) ''
ip -6 route replace ${cfg.defaultGateway6.address} dev ${cfg.defaultGateway6.interface} ${optionalString (cfg.defaultGateway6.metric != null)
"metric ${toString cfg.defaultGateway6.metric}"
} proto static
''}
ip -6 route replace default ${optionalString (cfg.defaultGateway6.metric != null)
"metric ${toString cfg.defaultGateway6.metric}"
} via "${cfg.defaultGateway6.address}" ${
optionalString (cfg.defaultGatewayWindowSize != null)
"window ${toString cfg.defaultGatewayWindowSize}"} ${
optionalString (cfg.defaultGateway6.interface != null)
"dev ${cfg.defaultGateway6.interface}"} proto static
''}
'';
};
# 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 "${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 add "${cidr}" dev "${i.name}" 2>&1); then
echo "done"
elif ! echo "$out" | grep "File exists" >/dev/null 2>&1; then
echo "'ip addr add "${cidr}" dev "${i.name}"' 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 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 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-setup.service" (subsystemDevice i.name) ];
partOf = [ "network-setup.service" ];
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 ${i.name} || true
'';
};
createBridgeDevice = n: v: nameValuePair "${n}-netdev"
(let
deps = concatLists (map deviceDependency v.interfaces);
in
{ description = "Bridge Interface ${n}";
wantedBy = [ "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 "${n}" >/dev/null 2>&1 && ip link del "${n}"
echo "Adding bridge ${n}..."
ip link add name "${n}" type bridge
# Enslave child interfaces
${flip concatMapStrings v.interfaces (i: ''
ip link set "${i}" master "${n}"
ip link set "${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 ',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 "${n}" up
'';
postStop = ''
ip link set "${n}" down || true
ip link del "${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 "$interface" nomaster up
done
# Enslave child interfaces (new list of interfaces)
${flip concatMapStrings v.interfaces (i: ''
ip link set "${i}" master "${n}"
ip link set "${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-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 "Shuting down internal ${n} interface"
ip link set ${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-setup.service" (subsystemDevice n) ];
bindsTo = deps;
partOf = [ "network-setup.service" ];
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 "${n}" up
${flip concatMapStrings v.interfaces (i: ''
ip link set "${i}" down
ip link set "${i}" master "${n}"
'')}
'';
postStop = destroyBond n;
});
createMacvlanDevice = n: v: nameValuePair "${n}-netdev"
(let
deps = deviceDependency v.interface;
in
{ description = "Vlan Interface ${n}";
wantedBy = [ "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 "${n}" >/dev/null 2>&1 && ip link delete "${n}"
ip link add link "${v.interface}" name "${n}" type macvlan \
${optionalString (v.mode != null) "mode ${v.mode}"}
ip link set "${n}" up
'';
postStop = ''
ip link delete "${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-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 = ''
# 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 = "6-to-4 Tunnel Interface ${n}";
wantedBy = [ "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 "${n}" >/dev/null 2>&1 && ip link delete "${n}"
ip link add name "${n}" type sit \
${optionalString (v.remote != null) "remote \"${v.remote}\""} \
${optionalString (v.local != null) "local \"${v.local}\""} \
${optionalString (v.ttl != null) "ttl ${toString v.ttl}"} \
${optionalString (v.dev != null) "dev \"${v.dev}\""} \
${optionalString (v.encapsulation != null)
"encap ${v.encapsulation.type} encap-dport ${toString v.encapsulation.port} ${
optionalString (v.encapsulation.sourcePort != null)
"encap-sport ${toString v.encapsulation.sourcePort}"
}"}
ip link set "${n}" up
'';
postStop = ''
ip link delete "${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-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 "${n}" >/dev/null 2>&1 && ip link delete "${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 "${n}" up
'';
postStop = ''
ip link delete "${n}" || true
'';
});
createVlanDevice = n: v: nameValuePair "${n}-netdev"
(let
deps = deviceDependency v.interface;
in
{ description = "Vlan Interface ${n}";
wantedBy = [ "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 "${n}" >/dev/null 2>&1 && ip link delete "${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 "${n}" up || true
'';
postStop = ''
ip link delete "${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' createGreDevice cfg.greTunnels
// mapAttrs' createVlanDevice cfg.vlans
// {
network-setup = networkSetup;
network-local-commands = networkLocalCommands;
};
services.udev.extraRules =
''
KERNEL=="tun", TAG+="systemd"
'';
};
in
{
config = mkMerge [
bondWarnings
(mkIf (!cfg.useNetworkd) normalConfig)
{ # Ensure slave interfaces are brought up
networking.interfaces = genAttrs slaves (i: {});
}
];
}

View file

@ -0,0 +1,441 @@
{ 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 (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);
in
{
config = 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 supported by networkd.";
} {
assertion = cfg.defaultGateway6 == null || cfg.defaultGateway6.interface == null;
message = "networking.defaultGateway6.interface is not supported by 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 =
let
domains = cfg.search ++ (optional (cfg.domain != null) cfg.domain);
genericNetwork = override:
let gateway = optional (cfg.defaultGateway != null && (cfg.defaultGateway.address or "") != "") cfg.defaultGateway.address
++ optional (cfg.defaultGateway6 != null && (cfg.defaultGateway6.address or "") != "") cfg.defaultGateway6.address;
in optionalAttrs (gateway != [ ]) {
routes = override [
{
routeConfig = {
Gateway = gateway;
GatewayOnLink = false;
};
}
];
} // optionalAttrs (domains != [ ]) {
domains = override domains;
};
in mkMerge [ {
enable = true;
}
(mkIf cfg.useDHCP {
networks."99-ethernet-default-dhcp" = lib.mkIf cfg.useDHCP {
# We want to match physical ethernet interfaces as commonly
# found on laptops, desktops and servers, to provide an
# "out-of-the-box" setup that works for common cases. This
# heuristic isn't perfect (it could match interfaces with
# custom names that _happen_ to start with en or eth), but
# should be good enough to make the common case easy and can
# be overridden on a case-by-case basis using
# higher-priority networks or by disabling useDHCP.
# Type=ether matches veth interfaces as well, and this is
# more likely to result in interfaces being configured to
# use DHCP when they shouldn't.
# We set RequiredForOnline to false, because it's fairly
# common for such devices to have multiple interfaces and
# only one of them to be connected (e.g. a laptop with
# ethernet and WiFi interfaces). Maybe one day networkd will
# support "any"-style RequiredForOnline...
matchConfig.Name = ["en*" "eth*"];
DHCP = "yes";
linkConfig.RequiredForOnline = lib.mkDefault false;
};
networks."99-wireless-client-dhcp" = lib.mkIf cfg.useDHCP {
# Like above, but this is much more likely to be correct.
matchConfig.WLANInterfaceType = "station";
DHCP = "yes";
linkConfig.RequiredForOnline = lib.mkDefault false;
# 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;
};
})
(mkMerge (forEach interfaces (i: {
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}" = mkMerge [ (genericNetwork id) {
name = mkDefault i.name;
DHCP = mkForce (dhcpStr
(if i.useDHCP != null then i.useDHCP else false));
address = forEach (interfaceIps i)
(ip: "${ip.address}/${toString ip.prefixLength}");
routes = forEach (interfaceRoutes i)
(route: {
# Most of these route options have not been tested.
# Please fix or report any mistakes you may find.
routeConfig =
optionalAttrs (route.prefixLength > 0) {
Destination = "${route.address}/${toString route.prefixLength}";
} //
optionalAttrs (route.options ? fastopen_no_cookie) {
FastOpenNoCookie = route.options.fastopen_no_cookie;
} //
optionalAttrs (route.via != null) {
Gateway = route.via;
} //
optionalAttrs (route.type != null) {
Type = route.type;
} //
optionalAttrs (route.options ? onlink) {
GatewayOnLink = true;
} //
optionalAttrs (route.options ? initrwnd) {
InitialAdvertisedReceiveWindow = route.options.initrwnd;
} //
optionalAttrs (route.options ? initcwnd) {
InitialCongestionWindow = route.options.initcwnd;
} //
optionalAttrs (route.options ? pref) {
IPv6Preference = route.options.pref;
} //
optionalAttrs (route.options ? mtu) {
MTUBytes = route.options.mtu;
} //
optionalAttrs (route.options ? metric) {
Metric = route.options.metric;
} //
optionalAttrs (route.options ? src) {
PreferredSource = route.options.src;
} //
optionalAttrs (route.options ? protocol) {
Protocol = route.options.protocol;
} //
optionalAttrs (route.options ? quickack) {
QuickAck = route.options.quickack;
} //
optionalAttrs (route.options ? scope) {
Scope = route.options.scope;
} //
optionalAttrs (route.options ? from) {
Source = route.options.from;
} //
optionalAttrs (route.options ? table) {
Table = route.options.table;
} //
optionalAttrs (route.options ? advmss) {
TCPAdvertisedMaximumSegmentSize = route.options.advmss;
} //
optionalAttrs (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;
};
}];
})))
(mkMerge (flip mapAttrsToList cfg.bridges (name: bridge: {
netdevs."40-${name}" = {
netdevConfig = {
Name = name;
Kind = "bridge";
};
};
networks = listToAttrs (forEach bridge.interfaces (bi:
nameValuePair "40-${bi}" (mkMerge [ (genericNetwork (mkOverride 999)) {
DHCP = mkOverride 0 (dhcpStr false);
networkConfig.Bridge = name;
} ])));
})))
(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}" (mkMerge [ (genericNetwork (mkOverride 999)) {
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}" = (mkMerge [ (genericNetwork (mkOverride 999)) {
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 != null) (
{
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}" = (mkMerge [ (genericNetwork (mkOverride 999)) {
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}" = (mkMerge [ (genericNetwork (mkOverride 999)) {
tunnel = [ name ];
} ]);
};
})))
(mkMerge (flip mapAttrsToList cfg.vlans (name: vlan: {
netdevs."40-${name}" = {
netdevConfig = {
Name = name;
Kind = "vlan";
};
vlanConfig.Id = vlan.id;
};
networks."40-${vlan.interface}" = (mkMerge [ (genericNetwork (mkOverride 999)) {
vlan = [ name ];
} ]);
})))
];
# 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 "Shuting down internal ${n} interface"
ip link set ${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,29 @@
{ config, lib, pkgs, ... }:
with lib;
let
cfg = config.powerManagement.powertop;
in {
###### interface
options.powerManagement.powertop.enable = mkEnableOption "powertop auto tuning on startup";
###### implementation
config = mkIf (cfg.enable) {
systemd.services = {
powertop = {
wantedBy = [ "multi-user.target" ];
after = [ "multi-user.target" ];
description = "Powertop tunings";
path = [ pkgs.kmod ];
serviceConfig = {
Type = "oneshot";
RemainAfterExit = "yes";
ExecStart = "${pkgs.powertop}/bin/powertop --auto-tune";
};
};
};
};
}

View file

@ -0,0 +1,54 @@
{ 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".
</para><para>
"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,230 @@
{ config, lib, pkgs, ... }:
with lib;
let cfg = config.snapraid;
in
{
options.snapraid = with types; {
enable = mkEnableOption "SnapRAID";
dataDisks = mkOption {
default = { };
example = {
d1 = "/mnt/disk1/";
d2 = "/mnt/disk2/";
d3 = "/mnt/disk3/";
};
description = "SnapRAID data disks.";
type = attrsOf str;
};
parityFiles = mkOption {
default = [ ];
example = [
"/mnt/diskp/snapraid.parity"
"/mnt/diskq/snapraid.2-parity"
"/mnt/diskr/snapraid.3-parity"
"/mnt/disks/snapraid.4-parity"
"/mnt/diskt/snapraid.5-parity"
"/mnt/disku/snapraid.6-parity"
];
description = "SnapRAID parity files.";
type = listOf str;
};
contentFiles = mkOption {
default = [ ];
example = [
"/var/snapraid.content"
"/mnt/disk1/snapraid.content"
"/mnt/disk2/snapraid.content"
];
description = "SnapRAID content list files.";
type = listOf str;
};
exclude = mkOption {
default = [ ];
example = [ "*.unrecoverable" "/tmp/" "/lost+found/" ];
description = "SnapRAID exclude directives.";
type = listOf str;
};
touchBeforeSync = mkOption {
default = true;
example = false;
description =
"Whether <command>snapraid touch</command> should be run before <command>snapraid sync</command>.";
type = bool;
};
sync.interval = mkOption {
default = "01:00";
example = "daily";
description = "How often to run <command>snapraid sync</command>.";
type = str;
};
scrub = {
interval = mkOption {
default = "Mon *-*-* 02:00:00";
example = "weekly";
description = "How often to run <command>snapraid scrub</command>.";
type = str;
};
plan = mkOption {
default = 8;
example = 5;
description =
"Percent of the array that should be checked by <command>snapraid scrub</command>.";
type = int;
};
olderThan = mkOption {
default = 10;
example = 20;
description =
"Number of days since data was last scrubbed before it can be scrubbed again.";
type = int;
};
};
extraConfig = mkOption {
default = "";
example = ''
nohidden
blocksize 256
hashsize 16
autosave 500
pool /pool
'';
description = "Extra config options for SnapRAID.";
type = lines;
};
};
config =
let
nParity = builtins.length cfg.parityFiles;
mkPrepend = pre: s: pre + s;
in
mkIf cfg.enable {
assertions = [
{
assertion = nParity <= 6;
message = "You can have no more than six SnapRAID parity files.";
}
{
assertion = builtins.length cfg.contentFiles >= nParity + 1;
message =
"There must be at least one SnapRAID content file for each SnapRAID parity file plus one.";
}
];
environment = {
systemPackages = with pkgs; [ snapraid ];
etc."snapraid.conf" = {
text = with cfg;
let
prependData = mkPrepend "data ";
prependContent = mkPrepend "content ";
prependExclude = mkPrepend "exclude ";
in
concatStringsSep "\n"
(map prependData
((mapAttrsToList (name: value: name + " " + value)) dataDisks)
++ zipListsWith (a: b: a + b)
([ "parity " ] ++ map (i: toString i + "-parity ") (range 2 6))
parityFiles ++ map prependContent contentFiles
++ map prependExclude exclude) + "\n" + extraConfig;
};
};
systemd.services = with cfg; {
snapraid-scrub = {
description = "Scrub the SnapRAID array";
startAt = scrub.interval;
serviceConfig = {
Type = "oneshot";
ExecStart = "${pkgs.snapraid}/bin/snapraid scrub -p ${
toString scrub.plan
} -o ${toString scrub.olderThan}";
Nice = 19;
IOSchedulingPriority = 7;
CPUSchedulingPolicy = "batch";
LockPersonality = true;
MemoryDenyWriteExecute = true;
NoNewPrivileges = true;
PrivateDevices = true;
PrivateTmp = true;
ProtectClock = true;
ProtectControlGroups = true;
ProtectHostname = true;
ProtectKernelLogs = true;
ProtectKernelModules = true;
ProtectKernelTunables = true;
RestrictAddressFamilies = "none";
RestrictNamespaces = true;
RestrictRealtime = true;
RestrictSUIDSGID = true;
SystemCallArchitectures = "native";
SystemCallFilter = "@system-service";
SystemCallErrorNumber = "EPERM";
CapabilityBoundingSet = "CAP_DAC_OVERRIDE";
ProtectSystem = "strict";
ProtectHome = "read-only";
ReadWritePaths =
# scrub requires access to directories containing content files
# to remove them if they are stale
let
contentDirs = map dirOf contentFiles;
in
unique (
attrValues dataDisks ++ contentDirs
);
};
unitConfig.After = "snapraid-sync.service";
};
snapraid-sync = {
description = "Synchronize the state of the SnapRAID array";
startAt = sync.interval;
serviceConfig = {
Type = "oneshot";
ExecStart = "${pkgs.snapraid}/bin/snapraid sync";
Nice = 19;
IOSchedulingPriority = 7;
CPUSchedulingPolicy = "batch";
LockPersonality = true;
MemoryDenyWriteExecute = true;
NoNewPrivileges = true;
PrivateTmp = true;
ProtectClock = true;
ProtectControlGroups = true;
ProtectHostname = true;
ProtectKernelLogs = true;
ProtectKernelModules = true;
ProtectKernelTunables = true;
RestrictAddressFamilies = "none";
RestrictNamespaces = true;
RestrictRealtime = true;
RestrictSUIDSGID = true;
SystemCallArchitectures = "native";
SystemCallFilter = "@system-service";
SystemCallErrorNumber = "EPERM";
CapabilityBoundingSet = "CAP_DAC_OVERRIDE" +
lib.optionalString cfg.touchBeforeSync " CAP_FOWNER";
ProtectSystem = "strict";
ProtectHome = "read-only";
ReadWritePaths =
# sync requires access to directories containing content files
# to remove them if they are stale
let
contentDirs = map dirOf contentFiles;
in
unique (
attrValues dataDisks ++ parityFiles ++ contentDirs
);
} // optionalAttrs touchBeforeSync {
ExecStartPre = "${pkgs.snapraid}/bin/snapraid touch";
};
};
};
};
}

View file

@ -0,0 +1,43 @@
{ config, pkgs, lib, ... }: let
cfg = config.boot.initrd.services.swraid;
in {
options.boot.initrd.services.swraid = {
enable = (lib.mkEnableOption "swraid support using mdadm") // {
visible = false; # only has effect when the new stage 1 is in place
};
mdadmConf = lib.mkOption {
description = "Contents of <filename>/etc/mdadm.conf</filename> in initrd.";
type = lib.types.lines;
default = "";
};
};
config = {
environment.systemPackages = [ pkgs.mdadm ];
services.udev.packages = [ pkgs.mdadm ];
systemd.packages = [ pkgs.mdadm ];
boot.initrd.availableKernelModules = lib.mkIf (config.boot.initrd.systemd.enable -> cfg.enable) [ "md_mod" "raid0" "raid1" "raid10" "raid456" ];
boot.initrd.extraUdevRulesCommands = lib.mkIf (!config.boot.initrd.systemd.enable) ''
cp -v ${pkgs.mdadm}/lib/udev/rules.d/*.rules $out/
'';
boot.initrd.systemd = lib.mkIf cfg.enable {
contents."/etc/mdadm.conf" = lib.mkIf (cfg.mdadmConf != "") {
text = cfg.mdadmConf;
};
packages = [ pkgs.mdadm ];
initrdBin = [ pkgs.mdadm ];
};
boot.initrd.services.udev.packages = lib.mkIf cfg.enable [ pkgs.mdadm ];
};
}

View file

@ -0,0 +1,108 @@
{ 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}"
'';
system.activationScripts.trackpoint =
''
${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";
})
];
}

View file

@ -0,0 +1,32 @@
source $stdenv/setup
ttys=($ttys)
themes=($themes)
mkdir -p $out
defaultName=$(cd $default && ls | grep -v default)
echo $defaultName
ln -s $default/$defaultName $out/$defaultName
ln -s $defaultName $out/default
for ((n = 0; n < ${#ttys[*]}; n++)); do
tty=${ttys[$n]}
theme=${themes[$n]}
echo "TTY $tty -> $theme"
if [ "$theme" != default ]; then
themeName=$(cd $theme && ls | grep -v default)
ln -sfn $theme/$themeName $out/$themeName
else
themeName=default
fi
if test -e $out/$tty; then
echo "Multiple themes defined for the same TTY!"
exit 1
fi
ln -sfn $themeName $out/$tty
done