diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix index df46702f3390..796bdd4f8603 100644 --- a/nixos/modules/module-list.nix +++ b/nixos/modules/module-list.nix @@ -158,6 +158,7 @@ ./programs/bash/ls-colors.nix ./programs/bash/undistract-me.nix ./programs/bcc.nix + ./programs/benchexec.nix ./programs/browserpass.nix ./programs/calls.nix ./programs/captive-browser.nix diff --git a/nixos/modules/programs/benchexec.nix b/nixos/modules/programs/benchexec.nix new file mode 100644 index 000000000000..652670c117ea --- /dev/null +++ b/nixos/modules/programs/benchexec.nix @@ -0,0 +1,98 @@ +{ lib +, pkgs +, config +, options +, ... +}: +let + cfg = config.programs.benchexec; + opt = options.programs.benchexec; + + filterUsers = x: + if builtins.isString x then config.users.users ? ${x} else + if builtins.isInt x then x else + throw "filterUsers expects string (username) or int (UID)"; + + uid = x: + if builtins.isString x then config.users.users.${x}.uid else + if builtins.isInt x then x else + throw "uid expects string (username) or int (UID)"; +in +{ + options.programs.benchexec = { + enable = lib.mkEnableOption "BenchExec"; + package = lib.options.mkPackageOption pkgs "benchexec" { }; + + users = lib.options.mkOption { + type = with lib.types; listOf (either str int); + description = '' + Users that intend to use BenchExec. + Provide usernames of users that are configured via {option}`${options.users.users}` as string, + and UIDs of "mutable users" as integers. + Control group delegation will be configured via systemd. + For more information, see . + ''; + default = [ ]; + example = lib.literalExpression '' + [ + "alice" # username of a user configured via ${options.users.users} + 1007 # UID of a mutable user + ] + ''; + }; + }; + + config = lib.mkIf cfg.enable { + assertions = (map + (user: { + assertion = config.users.users ? ${user}; + message = '' + The user '${user}' intends to use BenchExec (via `${opt.users}`), but is not configured via `${options.users.users}`. + ''; + }) + (builtins.filter builtins.isString cfg.users) + ) ++ (map + (id: { + assertion = config.users.mutableUsers; + message = '' + The user with UID '${id}' intends to use BenchExec (via `${opt.users}`), but mutable users are disabled via `${options.users.mutableUsers}`. + ''; + }) + (builtins.filter builtins.isInt cfg.users) + ) ++ [ + { + assertion = config.systemd.enableUnifiedCgroupHierarchy == true; + message = '' + The BenchExec module `${opt.enable}` only supports control groups 2 (`${options.systemd.enableUnifiedCgroupHierarchy} = true`). + ''; + } + ]; + + environment.systemPackages = [ cfg.package ]; + + # See . + systemd.services = builtins.listToAttrs (map + (user: { + name = "user@${builtins.toString (uid user)}"; + value = { + serviceConfig.Delegate = "yes"; + overrideStrategy = "asDropin"; + }; + }) + (builtins.filter filterUsers cfg.users)); + + # See . + virtualisation.lxc.lxcfs.enable = lib.mkDefault true; + + # See . + programs = { + cpu-energy-meter.enable = lib.mkDefault true; + pqos-wrapper.enable = lib.mkDefault true; + }; + + # See . + security.unprivilegedUsernsClone = true; + }; + + meta.maintainers = with lib.maintainers; [ lorenzleutgeb ]; +} diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix index 9af07688c03c..2ebe5fa7ed23 100644 --- a/nixos/tests/all-tests.nix +++ b/nixos/tests/all-tests.nix @@ -144,6 +144,7 @@ in { bcachefs = handleTestOn ["x86_64-linux" "aarch64-linux"] ./bcachefs.nix {}; beanstalkd = handleTest ./beanstalkd.nix {}; bees = handleTest ./bees.nix {}; + benchexec = handleTest ./benchexec.nix {}; binary-cache = handleTest ./binary-cache.nix {}; bind = handleTest ./bind.nix {}; bird = handleTest ./bird.nix {}; diff --git a/nixos/tests/benchexec.nix b/nixos/tests/benchexec.nix new file mode 100644 index 000000000000..3fc9ebc2c35f --- /dev/null +++ b/nixos/tests/benchexec.nix @@ -0,0 +1,54 @@ +import ./make-test-python.nix ({ pkgs, lib, ... }: +let + user = "alice"; +in +{ + name = "benchexec"; + + nodes.benchexec = { + imports = [ ./common/user-account.nix ]; + + programs.benchexec = { + enable = true; + users = [ user ]; + }; + }; + + testScript = { ... }: + let + runexec = lib.getExe' pkgs.benchexec "runexec"; + echo = builtins.toString pkgs.benchexec; + test = lib.getExe (pkgs.writeShellApplication rec { + name = "test"; + meta.mainProgram = name; + text = "echo '${echo}'"; + }); + wd = "/tmp"; + stdout = "${wd}/runexec.out"; + stderr = "${wd}/runexec.err"; + in + '' + start_all() + machine.wait_for_unit("multi-user.target") + benchexec.succeed(''''\ + systemd-run \ + --property='StandardOutput=file:${stdout}' \ + --property='StandardError=file:${stderr}' \ + --unit=runexec --wait --user --machine='${user}@' \ + --working-directory ${wd} \ + '${runexec}' \ + --debug \ + --read-only-dir / \ + --hidden-dir /home \ + '${test}' \ + '''') + benchexec.succeed("grep -s '${echo}' ${wd}/output.log") + benchexec.succeed("test \"$(grep -Ec '((start|wall|cpu)time|memory)=' ${stdout})\" = 4") + benchexec.succeed("! grep -E '(WARNING|ERROR)' ${stderr}") + ''; + + interactive.nodes.benchexec.services.kmscon = { + enable = true; + fonts = [{ name = "Fira Code"; package = pkgs.fira-code; }]; + }; +})