nixos/config/sysfs: init module
This commit is contained in:
parent
b6e09db847
commit
ba04f97d4e
266
nixos/modules/config/sysfs.nix
Normal file
266
nixos/modules/config/sysfs.nix
Normal file
@ -0,0 +1,266 @@
|
|||||||
|
{
|
||||||
|
lib,
|
||||||
|
config,
|
||||||
|
utils,
|
||||||
|
pkgs,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
|
||||||
|
let
|
||||||
|
inherit (lib)
|
||||||
|
all
|
||||||
|
any
|
||||||
|
concatLines
|
||||||
|
concatStringsSep
|
||||||
|
escapeShellArg
|
||||||
|
flatten
|
||||||
|
floatToString
|
||||||
|
foldl'
|
||||||
|
head
|
||||||
|
isAttrs
|
||||||
|
isDerivation
|
||||||
|
isFloat
|
||||||
|
isList
|
||||||
|
length
|
||||||
|
listToAttrs
|
||||||
|
match
|
||||||
|
mapAttrsToList
|
||||||
|
nameValuePair
|
||||||
|
removePrefix
|
||||||
|
tail
|
||||||
|
throwIf
|
||||||
|
;
|
||||||
|
|
||||||
|
inherit (lib.options)
|
||||||
|
showDefs
|
||||||
|
showOption
|
||||||
|
;
|
||||||
|
|
||||||
|
inherit (lib.strings)
|
||||||
|
escapeC
|
||||||
|
isConvertibleWithToString
|
||||||
|
;
|
||||||
|
|
||||||
|
inherit (lib.path.subpath) join;
|
||||||
|
|
||||||
|
inherit (utils) escapeSystemdPath;
|
||||||
|
|
||||||
|
cfg = config.boot.kernel.sysfs;
|
||||||
|
|
||||||
|
sysfsAttrs = with lib.types; nullOr (either sysfsValue (attrsOf sysfsAttrs));
|
||||||
|
sysfsValue = lib.mkOptionType {
|
||||||
|
name = "sysfs value";
|
||||||
|
description = "sysfs attribute value";
|
||||||
|
descriptionClass = "noun";
|
||||||
|
check = v: isConvertibleWithToString v;
|
||||||
|
merge =
|
||||||
|
loc: defs:
|
||||||
|
if length defs == 1 then
|
||||||
|
(head defs).value
|
||||||
|
else
|
||||||
|
(foldl' (
|
||||||
|
first: def:
|
||||||
|
# merge definitions if they produce the same value string
|
||||||
|
throwIf (mkValueString first.value != mkValueString def.value)
|
||||||
|
"The option \"${showOption loc}\" has conflicting definition values:${
|
||||||
|
showDefs [
|
||||||
|
first
|
||||||
|
def
|
||||||
|
]
|
||||||
|
}"
|
||||||
|
first
|
||||||
|
) (head defs) (tail defs)).value;
|
||||||
|
};
|
||||||
|
|
||||||
|
mapAttrsToListRecursive =
|
||||||
|
fn: set:
|
||||||
|
let
|
||||||
|
recurse =
|
||||||
|
p: v:
|
||||||
|
if isAttrs v && !isDerivation v then mapAttrsToList (n: v: recurse (p ++ [ n ]) v) v else fn p v;
|
||||||
|
in
|
||||||
|
flatten (recurse [ ] set);
|
||||||
|
|
||||||
|
mkPath = p: "/sys" + removePrefix "." (join p);
|
||||||
|
hasGlob = p: any (n: match ''(.*[^\\])?[*?[].*'' n != null) p;
|
||||||
|
|
||||||
|
mkValueString =
|
||||||
|
v:
|
||||||
|
# true will be converted to "1" by toString, saving one branch
|
||||||
|
if v == false then
|
||||||
|
"0"
|
||||||
|
else if isFloat v then
|
||||||
|
floatToString v # warn about loss of precision
|
||||||
|
else if isList v then
|
||||||
|
concatStringsSep "," (map mkValueString v)
|
||||||
|
else
|
||||||
|
toString v;
|
||||||
|
|
||||||
|
# escape whitespace and linebreaks, as well as the escape character itself,
|
||||||
|
# to ensure that field boundaries are always preserved
|
||||||
|
escapeTmpfiles = escapeC [
|
||||||
|
"\t"
|
||||||
|
"\n"
|
||||||
|
"\r"
|
||||||
|
" "
|
||||||
|
"\\"
|
||||||
|
];
|
||||||
|
|
||||||
|
tmpfiles = pkgs.runCommand "nixos-sysfs-tmpfiles.d" { } (
|
||||||
|
''
|
||||||
|
mkdir "$out"
|
||||||
|
''
|
||||||
|
+ concatLines (
|
||||||
|
mapAttrsToListRecursive (
|
||||||
|
p: v:
|
||||||
|
let
|
||||||
|
path = mkPath p;
|
||||||
|
in
|
||||||
|
if v == null then
|
||||||
|
[ ]
|
||||||
|
else
|
||||||
|
''
|
||||||
|
printf 'w %s - - - - %s\n' \
|
||||||
|
${escapeShellArg (escapeTmpfiles path)} \
|
||||||
|
${escapeShellArg (escapeTmpfiles (mkValueString v))} \
|
||||||
|
>"$out"/${escapeShellArg (escapeSystemdPath path)}.conf
|
||||||
|
''
|
||||||
|
) cfg
|
||||||
|
)
|
||||||
|
);
|
||||||
|
in
|
||||||
|
{
|
||||||
|
options = {
|
||||||
|
boot.kernel.sysfs = lib.mkOption {
|
||||||
|
type = lib.types.submodule {
|
||||||
|
freeformType = lib.types.attrsOf sysfsAttrs // {
|
||||||
|
description = "nested attribute set of null or sysfs attribute values";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
description = ''
|
||||||
|
sysfs attributes to be set as soon as they become available.
|
||||||
|
|
||||||
|
Attribute names represent path components in the sysfs filesystem and
|
||||||
|
cannot be `.` or `..` nor contain any slash character (`/`).
|
||||||
|
|
||||||
|
Names may contain shell‐style glob patterns (`*`, `?` and `[…]`)
|
||||||
|
matching a single path component, these should however be used with
|
||||||
|
caution, as they may produce unexpected results if attribute paths
|
||||||
|
overlap.
|
||||||
|
|
||||||
|
Values will be converted to strings, with list elements concatenated
|
||||||
|
with commata and booleans converted to numeric values (`0` or `1`).
|
||||||
|
|
||||||
|
`null` values are ignored, allowing removal of values defined in other
|
||||||
|
modules, as are empty attribute sets.
|
||||||
|
|
||||||
|
List values defined in different modules will _not_ be concatenated.
|
||||||
|
|
||||||
|
This option may only be used for attributes which can be set
|
||||||
|
idempotently, as the configured values might be written more than once.
|
||||||
|
'';
|
||||||
|
|
||||||
|
default = { };
|
||||||
|
|
||||||
|
example = lib.literalExpression ''
|
||||||
|
{
|
||||||
|
# enable transparent hugepages with deferred defragmentaion
|
||||||
|
kernel.mm.transparent_hugepage = {
|
||||||
|
enabled = "always";
|
||||||
|
defrag = "defer";
|
||||||
|
shmem_enabled = "within_size";
|
||||||
|
};
|
||||||
|
|
||||||
|
devices.system.cpu = {
|
||||||
|
# configure powesave frequency governor for all CPUs
|
||||||
|
# the [0-9]* glob pattern ensures that other paths
|
||||||
|
# like cpufreq or cpuidle are not matched
|
||||||
|
"cpu[0-9]*" = {
|
||||||
|
scaling_governor = "powersave";
|
||||||
|
energy_performance_preference = 8;
|
||||||
|
};
|
||||||
|
|
||||||
|
# disable frequency boost
|
||||||
|
intel_pstate.no_turbo = true;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
config = lib.mkIf (cfg != { }) {
|
||||||
|
systemd = {
|
||||||
|
paths =
|
||||||
|
{
|
||||||
|
"nixos-sysfs@" = {
|
||||||
|
description = "/%I attribute watcher";
|
||||||
|
pathConfig.PathExistsGlob = "/%I";
|
||||||
|
unitConfig.DefaultDependencies = false;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
// listToAttrs (
|
||||||
|
mapAttrsToListRecursive (
|
||||||
|
p: v:
|
||||||
|
if v == null then
|
||||||
|
[ ]
|
||||||
|
else
|
||||||
|
nameValuePair "nixos-sysfs@${escapeSystemdPath (mkPath p)}" {
|
||||||
|
overrideStrategy = "asDropin";
|
||||||
|
wantedBy = [ "sysinit.target" ];
|
||||||
|
before = [ "sysinit.target" ];
|
||||||
|
}
|
||||||
|
) cfg
|
||||||
|
);
|
||||||
|
|
||||||
|
services."nixos-sysfs@" = {
|
||||||
|
description = "/%I attribute setter";
|
||||||
|
|
||||||
|
unitConfig = {
|
||||||
|
DefaultDependencies = false;
|
||||||
|
AssertPathIsMountPoint = "/sys";
|
||||||
|
AssertPathExistsGlob = "/%I";
|
||||||
|
};
|
||||||
|
|
||||||
|
serviceConfig = {
|
||||||
|
Type = "oneshot";
|
||||||
|
RemainAfterExit = true;
|
||||||
|
|
||||||
|
# while we could be tempted to use simple shell script to set the
|
||||||
|
# sysfs attributes specified by the path or glob pattern, it is
|
||||||
|
# almost impossible to properly escape a glob pattern so that it
|
||||||
|
# can be used safely in a shell script
|
||||||
|
ExecStart = "${lib.getExe' config.systemd.package "systemd-tmpfiles"} --prefix=/sys --create ${tmpfiles}/%i.conf";
|
||||||
|
|
||||||
|
# hardening may be overkill for such a simple and short‐lived
|
||||||
|
# service, the following settings would however be suitable to deny
|
||||||
|
# access to anything but /sys
|
||||||
|
#ProtectProc = "noaccess";
|
||||||
|
#ProcSubset = "pid";
|
||||||
|
#ProtectSystem = "strict";
|
||||||
|
#PrivateDevices = true;
|
||||||
|
#SystemCallErrorNumber = "EPERM";
|
||||||
|
#SystemCallFilter = [
|
||||||
|
# "@basic-io"
|
||||||
|
# "@file-system"
|
||||||
|
#];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
warnings = mapAttrsToListRecursive (
|
||||||
|
p: v:
|
||||||
|
if hasGlob p then
|
||||||
|
"Attribute path \"${concatStringsSep "." p}\" contains glob patterns. Please ensure that it does not overlap with other paths."
|
||||||
|
else
|
||||||
|
[ ]
|
||||||
|
) cfg;
|
||||||
|
|
||||||
|
assertions = mapAttrsToListRecursive (p: v: {
|
||||||
|
assertion = all (n: match ''(\.\.?|.*/.*)'' n == null) p;
|
||||||
|
message = "Attribute path \"${concatStringsSep "." p}\" has invalid components.";
|
||||||
|
}) cfg;
|
||||||
|
};
|
||||||
|
|
||||||
|
meta.maintainers = with lib.maintainers; [ mvs ];
|
||||||
|
}
|
@ -31,6 +31,7 @@
|
|||||||
./config/stub-ld.nix
|
./config/stub-ld.nix
|
||||||
./config/swap.nix
|
./config/swap.nix
|
||||||
./config/sysctl.nix
|
./config/sysctl.nix
|
||||||
|
./config/sysfs.nix
|
||||||
./config/system-environment.nix
|
./config/system-environment.nix
|
||||||
./config/system-path.nix
|
./config/system-path.nix
|
||||||
./config/terminfo.nix
|
./config/terminfo.nix
|
||||||
|
@ -1394,6 +1394,7 @@ in
|
|||||||
syncthing-folders = runTest ./syncthing-folders.nix;
|
syncthing-folders = runTest ./syncthing-folders.nix;
|
||||||
syncthing-relay = runTest ./syncthing-relay.nix;
|
syncthing-relay = runTest ./syncthing-relay.nix;
|
||||||
sysinit-reactivation = runTest ./sysinit-reactivation.nix;
|
sysinit-reactivation = runTest ./sysinit-reactivation.nix;
|
||||||
|
sysfs = runTest ./sysfs.nix;
|
||||||
systemd = runTest ./systemd.nix;
|
systemd = runTest ./systemd.nix;
|
||||||
systemd-analyze = runTest ./systemd-analyze.nix;
|
systemd-analyze = runTest ./systemd-analyze.nix;
|
||||||
systemd-binfmt = handleTestOn [ "x86_64-linux" ] ./systemd-binfmt.nix { };
|
systemd-binfmt = handleTestOn [ "x86_64-linux" ] ./systemd-binfmt.nix { };
|
||||||
|
37
nixos/tests/sysfs.nix
Normal file
37
nixos/tests/sysfs.nix
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
{ lib, ... }:
|
||||||
|
|
||||||
|
{
|
||||||
|
name = "sysfs";
|
||||||
|
meta.maintainers = with lib.maintainers; [ mvs ];
|
||||||
|
|
||||||
|
nodes.machine = {
|
||||||
|
boot.kernel.sysfs = {
|
||||||
|
kernel.mm.transparent_hugepage = {
|
||||||
|
enabled = "always";
|
||||||
|
defrag = "defer";
|
||||||
|
shmem_enabled = "within_size";
|
||||||
|
};
|
||||||
|
|
||||||
|
block."*".queue.scheduler = "none";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
testScript =
|
||||||
|
{ nodes, ... }:
|
||||||
|
let
|
||||||
|
inherit (nodes.machine.boot.kernel) sysfs;
|
||||||
|
in
|
||||||
|
''
|
||||||
|
from shlex import quote
|
||||||
|
|
||||||
|
def check(filename, contents):
|
||||||
|
machine.succeed('grep -F -q {} {}'.format(quote(contents), quote(filename)))
|
||||||
|
|
||||||
|
check('/sys/kernel/mm/transparent_hugepage/enabled',
|
||||||
|
'[${sysfs.kernel.mm.transparent_hugepage.enabled}]')
|
||||||
|
check('/sys/kernel/mm/transparent_hugepage/defrag',
|
||||||
|
'[${sysfs.kernel.mm.transparent_hugepage.defrag}]')
|
||||||
|
check('/sys/kernel/mm/transparent_hugepage/shmem_enabled',
|
||||||
|
'[${sysfs.kernel.mm.transparent_hugepage.shmem_enabled}]')
|
||||||
|
'';
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user