diff --git a/modules/systemd.nix b/modules/systemd.nix index 22490218..18e88819 100644 --- a/modules/systemd.nix +++ b/modules/systemd.nix @@ -6,10 +6,16 @@ let inherit (lib) getAttr hm isBool literalExpression mkIf mkMerge mkOption types; + settingsFormat = pkgs.formats.ini { listsAsDuplicateKeys = true; }; + # From mkPathSafeName = lib.replaceStrings [ "@" ":" "\\" "[" "]" ] [ "-" "-" "-" "" "" ]; + removeIfEmpty = attrs: names: + lib.filterAttrs (name: value: !(builtins.elem name names) || value != "") + attrs; + toSystemdIni = lib.generators.toINI { listsAsDuplicateKeys = true; mkKeyValue = key: value: @@ -87,6 +93,11 @@ let + "\n"; }; + settings = mkIf (cfg.settings != { }) { + "systemd/user.conf".source = + settingsFormat.generate "user.conf" cfg.settings; + }; + in { meta.maintainers = [ lib.maintainers.rycee ]; @@ -209,6 +220,64 @@ in { {manpage}`environment.d(5)`. ''; }; + + settings = mkOption { + apply = sections: + sections // { + # Setting one of these to an empty value would reset any + # previous settings, so we’ll remove them instead if they + # are not explicitly set. + Manager = removeIfEmpty sections.Manager [ + "ManagerEnvironment" + "DefaultEnvironment" + ]; + }; + + type = types.submodule { + freeformType = settingsFormat.type; + + options = let + inherit (lib) concatStringsSep escapeShellArg mapAttrsToList; + environmentOption = args: + mkOption { + type = with types; + attrsOf (nullOr (oneOf [ str path package ])); + default = { }; + example = literalExpression '' + { + PATH = "%u/bin:%u/.cargo/bin"; + } + ''; + apply = value: + concatStringsSep " " + (mapAttrsToList (n: v: "${n}=${escapeShellArg v}") value); + } // args; + in { + Manager = { + DefaultEnvironment = environmentOption { + description = '' + Configures environment variables passed to all executed processes. + ''; + }; + ManagerEnvironment = environmentOption { + description = '' + Sets environment variables just for the manager process itself. + ''; + }; + }; + }; + }; + default = { }; + example = literalExpression '' + { + Manager.DefaultCPUAccounting = true; + } + ''; + description = '' + Extra config options for user session service manager. See {manpage}`systemd-user.conf(5)` for + available options. + ''; + }; }; }; @@ -227,6 +296,8 @@ in { ++ (buildServices "automount" cfg.automounts))) sessionVariables + + settings ]; # Run systemd service reload if user is logged in. If we're diff --git a/tests/modules/systemd/default.nix b/tests/modules/systemd/default.nix index a0271b47..250b8c79 100644 --- a/tests/modules/systemd/default.nix +++ b/tests/modules/systemd/default.nix @@ -2,6 +2,7 @@ systemd-services = ./services.nix; systemd-services-disabled-for-root = ./services-disabled-for-root.nix; systemd-session-variables = ./session-variables.nix; + systemd-user-config = ./user-config.nix; systemd-slices = ./slices.nix; systemd-timers = ./timers.nix; } diff --git a/tests/modules/systemd/user-config.nix b/tests/modules/systemd/user-config.nix new file mode 100644 index 00000000..f977d2f1 --- /dev/null +++ b/tests/modules/systemd/user-config.nix @@ -0,0 +1,25 @@ +{ pkgs, ... }: + +{ + systemd.user.settings.Manager = { + LogLevel = "debug"; + DefaultCPUAccounting = true; + DefaultEnvironment = { + TEST = "abc"; + PATH = "/bin:/sbin:/some where"; + }; + }; + + nmt.script = '' + userConf=home-files/.config/systemd/user.conf + assertFileExists $userConf + assertFileContent $userConf ${ + pkgs.writeText "expected" '' + [Manager] + DefaultCPUAccounting=true + DefaultEnvironment=PATH='/bin:/sbin:/some where' TEST='abc' + LogLevel=debug + '' + } + ''; +}