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:
commit
56de2bcd43
30691 changed files with 3076956 additions and 0 deletions
253
nixos/modules/tasks/auto-upgrade.nix
Normal file
253
nixos/modules/tasks/auto-upgrade.nix
Normal 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;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
23
nixos/modules/tasks/bcache.nix
Normal file
23
nixos/modules/tasks/bcache.nix
Normal 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 ];
|
||||
};
|
||||
};
|
||||
}
|
||||
90
nixos/modules/tasks/cpu-freq.nix
Normal file
90
nixos/modules/tasks/cpu-freq.nix
Normal 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";
|
||||
};
|
||||
};
|
||||
|
||||
};
|
||||
}
|
||||
87
nixos/modules/tasks/encrypted-devices.nix
Normal file
87
nixos/modules/tasks/encrypted-devices.nix
Normal 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.<name?>.device</literal> to <literal>/dev/mapper/<label></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.<name?>.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;
|
||||
};
|
||||
};
|
||||
}
|
||||
414
nixos/modules/tasks/filesystems.nix
Normal file
414
nixos/modules/tasks/filesystems.nix
Normal 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" ]; };
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
22
nixos/modules/tasks/filesystems/apfs.nix
Normal file
22
nixos/modules/tasks/filesystems/apfs.nix
Normal 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
|
||||
};
|
||||
}
|
||||
65
nixos/modules/tasks/filesystems/bcachefs.nix
Normal file
65
nixos/modules/tasks/filesystems/bcachefs.nix
Normal 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);
|
||||
})
|
||||
]);
|
||||
}
|
||||
149
nixos/modules/tasks/filesystems/btrfs.nix
Normal file
149
nixos/modules/tasks/filesystems/btrfs.nix
Normal 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);
|
||||
})
|
||||
];
|
||||
}
|
||||
25
nixos/modules/tasks/filesystems/cifs.nix
Normal file
25
nixos/modules/tasks/filesystems/cifs.nix
Normal 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
|
||||
'';
|
||||
|
||||
};
|
||||
}
|
||||
24
nixos/modules/tasks/filesystems/ecryptfs.nix
Normal file
24
nixos/modules/tasks/filesystems/ecryptfs.nix
Normal 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";
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
13
nixos/modules/tasks/filesystems/exfat.nix
Normal file
13
nixos/modules/tasks/filesystems/exfat.nix
Normal 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
|
||||
];
|
||||
};
|
||||
}
|
||||
28
nixos/modules/tasks/filesystems/ext.nix
Normal file
28
nixos/modules/tasks/filesystems/ext.nix
Normal 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
|
||||
'';
|
||||
|
||||
};
|
||||
}
|
||||
25
nixos/modules/tasks/filesystems/f2fs.nix
Normal file
25
nixos/modules/tasks/filesystems/f2fs.nix
Normal 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
|
||||
''}
|
||||
|
||||
'';
|
||||
};
|
||||
}
|
||||
11
nixos/modules/tasks/filesystems/glusterfs.nix
Normal file
11
nixos/modules/tasks/filesystems/glusterfs.nix
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
{
|
||||
config = mkIf (any (fs: fs == "glusterfs") config.boot.supportedFilesystems) {
|
||||
|
||||
system.fsPackages = [ pkgs.glusterfs ];
|
||||
|
||||
};
|
||||
}
|
||||
19
nixos/modules/tasks/filesystems/jfs.nix
Normal file
19
nixos/modules/tasks/filesystems/jfs.nix
Normal 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
|
||||
'';
|
||||
};
|
||||
}
|
||||
135
nixos/modules/tasks/filesystems/nfs.nix
Normal file
135
nixos/modules/tasks/filesystems/nfs.nix
Normal 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}
|
||||
'';
|
||||
};
|
||||
|
||||
};
|
||||
}
|
||||
11
nixos/modules/tasks/filesystems/ntfs.nix
Normal file
11
nixos/modules/tasks/filesystems/ntfs.nix
Normal 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 ];
|
||||
|
||||
};
|
||||
}
|
||||
25
nixos/modules/tasks/filesystems/reiserfs.nix
Normal file
25
nixos/modules/tasks/filesystems/reiserfs.nix
Normal 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
|
||||
'';
|
||||
|
||||
};
|
||||
}
|
||||
43
nixos/modules/tasks/filesystems/unionfs-fuse.nix
Normal file
43
nixos/modules/tasks/filesystems/unionfs-fuse.nix
Normal 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 ];
|
||||
})
|
||||
|
||||
];
|
||||
}
|
||||
23
nixos/modules/tasks/filesystems/vboxsf.nix
Normal file
23
nixos/modules/tasks/filesystems/vboxsf.nix
Normal 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" ];
|
||||
|
||||
};
|
||||
}
|
||||
25
nixos/modules/tasks/filesystems/vfat.nix
Normal file
25
nixos/modules/tasks/filesystems/vfat.nix
Normal 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
|
||||
'';
|
||||
|
||||
};
|
||||
}
|
||||
30
nixos/modules/tasks/filesystems/xfs.nix
Normal file
30
nixos/modules/tasks/filesystems/xfs.nix
Normal 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
|
||||
'';
|
||||
};
|
||||
}
|
||||
806
nixos/modules/tasks/filesystems/zfs.nix
Normal file
806
nixos/modules/tasks/filesystems/zfs.nix
Normal 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 <pool-name>", 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
133
nixos/modules/tasks/lvm.nix
Normal 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
|
||||
'';
|
||||
})
|
||||
];
|
||||
|
||||
}
|
||||
628
nixos/modules/tasks/network-interfaces-scripted.nix
Normal file
628
nixos/modules/tasks/network-interfaces-scripted.nix
Normal 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: {});
|
||||
}
|
||||
];
|
||||
}
|
||||
441
nixos/modules/tasks/network-interfaces-systemd.nix
Normal file
441
nixos/modules/tasks/network-interfaces-systemd.nix
Normal 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 don’t 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" ];
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
}
|
||||
1560
nixos/modules/tasks/network-interfaces.nix
Normal file
1560
nixos/modules/tasks/network-interfaces.nix
Normal file
File diff suppressed because it is too large
Load diff
29
nixos/modules/tasks/powertop.nix
Normal file
29
nixos/modules/tasks/powertop.nix
Normal 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";
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
54
nixos/modules/tasks/scsi-link-power-management.nix
Normal file
54
nixos/modules/tasks/scsi-link-power-management.nix
Normal 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}"
|
||||
'';
|
||||
};
|
||||
|
||||
}
|
||||
230
nixos/modules/tasks/snapraid.nix
Normal file
230
nixos/modules/tasks/snapraid.nix
Normal 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";
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
43
nixos/modules/tasks/swraid.nix
Normal file
43
nixos/modules/tasks/swraid.nix
Normal 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 ];
|
||||
};
|
||||
}
|
||||
108
nixos/modules/tasks/trackpoint.nix
Normal file
108
nixos/modules/tasks/trackpoint.nix
Normal 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";
|
||||
})
|
||||
];
|
||||
}
|
||||
32
nixos/modules/tasks/tty-backgrounds-combine.sh
Normal file
32
nixos/modules/tasks/tty-backgrounds-combine.sh
Normal 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
|
||||
Loading…
Add table
Add a link
Reference in a new issue