diff --git a/modules/misc/tmpfiles.nix b/modules/misc/tmpfiles.nix index 531e3cd87..15fc5c38d 100644 --- a/modules/misc/tmpfiles.nix +++ b/modules/misc/tmpfiles.nix @@ -9,175 +9,6 @@ let cfg = config.systemd.user.tmpfiles; - ruleSubmodule = lib.types.submodule ( - { name, ... }: - { - options.type = lib.mkOption { - type = lib.types.str; - readOnly = true; - default = name; - defaultText = "‹tmpfiles-type›"; - example = "d"; - description = '' - The type of operation to perform on the file. - - The type consists of a single letter and optionally one or more - modifier characters. - - Please see the upstream documentation for the available types and - more details: {manpage}`tmpfiles.d(5)` - ''; - }; - options.mode = lib.mkOption { - type = lib.types.str; - default = "-"; - example = "0755"; - description = '' - The file access mode to use when creating this file or directory. - ''; - }; - options.user = lib.mkOption { - type = lib.types.str; - default = "-"; - example = "root"; - description = '' - The user of the file. - - This may either be a numeric ID or a user/group name. - - If omitted or when set to `"-"`, the user and group of the user who - invokes systemd-tmpfiles is used. - ''; - }; - options.group = lib.mkOption { - type = lib.types.str; - default = "-"; - example = "root"; - description = '' - The group of the file. - - This may either be a numeric ID or a user/group name. - - If omitted or when set to `"-"`, the user and group of the user who - invokes systemd-tmpfiles is used. - ''; - }; - options.age = lib.mkOption { - type = lib.types.str; - default = "-"; - example = "10d"; - description = '' - Delete a file when it reaches a certain age. - - If a file or directory is older than the current time minus the age - field, it is deleted. - - If set to `"-"`, no automatic clean-up is done. - ''; - }; - options.argument = lib.mkOption { - type = lib.types.str; - default = ""; - example = ""; - description = '' - An argument whose meaning depends on the type of operation. - - Please see the upstream documentation for the meaning of this - parameter in different situations: {manpage}`tmpfiles.d(5)` - ''; - }; - } - ); - - attrsWith' = placeholder: elemType: lib.types.attrsWith { inherit elemType placeholder; }; - - nonEmptyAttrsWith' = - placeholder: elemType: - let - attrs = lib.types.addCheck (attrsWith' placeholder elemType) (s: s != { }); - in - attrs - // { - name = "nonEmptyAttrsOf"; - description = "non-empty ${attrs.description}"; - emptyValue = { }; # no .value attribute, meaning there is not empty value - substSubModules = m: nonEmptyAttrsWith' placeholder (elemType.substSubModules m); - }; - - configSubmodule = lib.types.submodule { - options.rules = lib.mkOption { - description = "The rules contained in this configuration."; - example = { - "%C".d = { - mode = "0755"; - user = "alice"; - group = "alice"; - age = "4 weeks"; - }; - }; - type = nonEmptyAttrsWith' "path" (nonEmptyAttrsWith' "tmpfiles-type" ruleSubmodule); - }; - options.purgeOnChange = lib.mkOption { - description = '' - Whether the rules that are marked for purging, will automatically - be purged when the set of rules changes. - - See {manpage}`systemd-tmpfiles(8)` for details about purging. - ''; - type = lib.types.bool; - default = false; - }; - }; - - modulePrefix = [ - "systemd" - "user" - "tmpfiles" - "settings" - ]; - - mkFileName = configName: "user-tmpfiles.d/home-manager-${configName}.conf"; - - mkConfigFile = - name: rules: - { - suffix ? [ name ], - }: - let - escapeArgument = lib.strings.escapeC [ - "\t" - "\n" - "\r" - " " - "\\" - ]; - mkRule = path: rule: '' - '${rule.type}' '${path}' '${rule.mode}' '${rule.user}' '${rule.group}' '${rule.age}' ${escapeArgument rule.argument} - ''; - in - { - text = '' - # This file was generated by Home Manager and should not be modified. - # Please change the option '${lib.showAttrPath (modulePrefix ++ suffix)}' instead. - ${lib.pipe rules [ - (lib.mapAttrs (_path: lib.attrValues)) - (lib.mapAttrsToList (path: map (mkRule path))) - lib.flatten - lib.concatStrings - ]} - ''; - onChange = '' - run ${pkgs.systemd}/bin/systemd-tmpfiles --user --remove --create ''${DRY_RUN:+--dry-run} '${config.xdg.configHome}/${mkFileName name}' - ''; - }; - - nonPurgedConfigs = lib.filterAttrs (_name: config: !config.purgeOnChange) cfg.settings; - purgedConfigs = lib.filterAttrs (_name: config: config.purgeOnChange) cfg.settings; - - # WARNING: When changing this path, the next home-manager generation will - # not find and the rules of the old generation that are subject to purging. - purgedRulesConfigName = "purge-on-change"; - in { meta.maintainers = with lib.maintainers; [ @@ -185,93 +16,42 @@ in dawidsowa ]; - imports = [ - (lib.mkRemovedOptionModule [ "systemd" "user" "tmpfiles" "rules" ] '' - It has been replaced by 'systemd.user.tmpfiles.settings'. - '') - ]; - - options.systemd.user.tmpfiles.settings = lib.mkOption { + options.systemd.user.tmpfiles.rules = lib.mkOption { + type = lib.types.listOf lib.types.str; + default = [ ]; + example = [ "L /home/user/Documents - - - - /mnt/data/Documents" ]; description = '' - Declare systemd-tmpfiles rules to create, delete, and clean up volatile - and temporary files and directories. - - Even though the service is called `*tmp*files` you can also create - persistent files. + Rules for creating and cleaning up temporary files + automatically. See + {manpage}`tmpfiles.d(5)` + for the exact format. ''; - example = { - cache.rules."%C".d = { - mode = "0755"; - user = "alice"; - group = "alice"; - age = "4 weeks"; - }; - }; - default = { }; - type = attrsWith' "config-name" configSubmodule; }; - config = lib.mkMerge [ + config = lib.mkIf (cfg.rules != [ ]) { + assertions = [ + (lib.hm.assertions.assertPlatform "systemd.user.tmpfiles" pkgs lib.platforms.linux) + ]; - (lib.mkIf pkgs.stdenv.hostPlatform.isLinux { - # The activation script must be enabled unconditionally in order to - # guarantee that the old rules are purged even if the new set of rules - # is empty, i.e. `cfg.rulesToPurgeOnChange == [ ]`. - home.activation.purgeTmpfiles = lib.hm.dag.entryAfter [ "writeBoundary" ] ( - let - relativeXdgConfigHome = lib.strings.removePrefix "${config.home.homeDirectory}/" config.xdg.configHome; - configPath = "home-files/${relativeXdgConfigHome}/${purgedRulesConfigName}"; - in - '' - if [[ -v oldGenPath && -f $oldGenPath/${configPath} ]] && - diff -q $oldGenPath/${configPath} $newGenPath/${configPath} &>/dev/null; then - verboseEcho "Purge old tmpfiles" - run ${pkgs.systemd}/bin/systemd-tmpfiles --user --purge ''${DRY_RUN:+--dry-run} $oldGenPath/${configPath} - fi - '' - ); - }) - - (lib.mkIf (cfg.settings != { }) { - assertions = [ - (lib.hm.assertions.assertPlatform "systemd.user.tmpfiles" pkgs lib.platforms.linux) - ]; - - warnings = lib.flatten ( - lib.mapAttrsToListRecursive ( - path: value: - lib.optional - (lib.last path == "argument" && lib.match ''.*\\([nrt]|x[0-9A-Fa-f]{2}).*'' value != null) - '' - The '${lib.showAttrPath (modulePrefix ++ path)}' option - appears to contain escape sequences, which will be escaped again. - Unescape them if this is not intended. The assigned string is: - "${value}" - '' - ) cfg.settings - ); - - xdg.configFile = { - "systemd/user/basic.target.wants/systemd-tmpfiles-setup.service".source = - "${pkgs.systemd}/example/systemd/user/systemd-tmpfiles-setup.service"; - "systemd/user/systemd-tmpfiles-setup.service".source = - "${pkgs.systemd}/example/systemd/user/systemd-tmpfiles-setup.service"; - "systemd/user/timers.target.wants/systemd-tmpfiles-clean.timer".source = - "${pkgs.systemd}/example/systemd/user/systemd-tmpfiles-clean.timer"; - "systemd/user/systemd-tmpfiles-clean.service".source = - "${pkgs.systemd}/example/systemd/user/systemd-tmpfiles-clean.service"; - } - // lib.mapAttrs' ( - name: config: lib.nameValuePair (mkFileName name) (mkConfigFile name config.rules { }) - ) nonPurgedConfigs - // lib.optionalAttrs (purgedConfigs != { }) { - ${mkFileName purgedRulesConfigName} = - let - purgedConfigsMerged = lib.foldl' lib.recursiveUpdate { } (lib.attrValues purgedConfigs); - in - mkConfigFile purgedRulesConfigName purgedConfigsMerged.rules { suffix = [ ]; }; + xdg.configFile = { + "user-tmpfiles.d/home-manager.conf" = { + text = '' + # This file is created automatically and should not be modified. + # Please change the option ‘systemd.user.tmpfiles.rules’ instead. + ${lib.concatStringsSep "\n" cfg.rules} + ''; + onChange = '' + run ${pkgs.systemd}/bin/systemd-tmpfiles --user --remove --create ''${DRY_RUN:+--dry-run} + ''; }; - }) - - ]; + "systemd/user/basic.target.wants/systemd-tmpfiles-setup.service".source = + "${pkgs.systemd}/example/systemd/user/systemd-tmpfiles-setup.service"; + "systemd/user/systemd-tmpfiles-setup.service".source = + "${pkgs.systemd}/example/systemd/user/systemd-tmpfiles-setup.service"; + "systemd/user/timers.target.wants/systemd-tmpfiles-clean.timer".source = + "${pkgs.systemd}/example/systemd/user/systemd-tmpfiles-clean.timer"; + "systemd/user/systemd-tmpfiles-clean.service".source = + "${pkgs.systemd}/example/systemd/user/systemd-tmpfiles-clean.service"; + }; + }; } diff --git a/modules/programs/glab.nix b/modules/programs/glab.nix index 84d1817ea..525456dc4 100644 --- a/modules/programs/glab.nix +++ b/modules/programs/glab.nix @@ -36,12 +36,14 @@ in # Use `systemd-tmpfiles` since glab requires its configuration file to have # mode 0600. - systemd.user.tmpfiles.settings.glab = lib.mkIf (cfg.settings != { }) { - rules."${config.xdg.configHome}/glab-cli/config.yml" = { - "C+$".argument = "${yaml.generate "glab-config" cfg.settings}"; - z.mode = "0600"; - }; - }; + systemd.user.tmpfiles.rules = + let + target = "${config.xdg.configHome}/glab-cli/config.yml"; + in + lib.mkIf (cfg.settings != { }) [ + "C+ ${target} - - - - ${yaml.generate "glab-config" cfg.settings}" + "z ${target} 0600" + ]; xdg.configFile."glab-cli/aliases.yml" = lib.mkIf (cfg.aliases != { }) { source = yaml.generate "glab-aliases" cfg.aliases; diff --git a/tests/default.nix b/tests/default.nix index 735e1f246..88ff40ece 100644 --- a/tests/default.nix +++ b/tests/default.nix @@ -203,7 +203,6 @@ import nmtSrc { ./modules/misc/numlock ./modules/misc/pam ./modules/misc/qt - ./modules/misc/tmpfiles ./modules/misc/xdg/linux.nix ./modules/misc/xsession ./modules/systemd diff --git a/tests/integration/standalone/rclone/sops-nix.nix b/tests/integration/standalone/rclone/sops-nix.nix index fc676505e..71283edf5 100644 --- a/tests/integration/standalone/rclone/sops-nix.nix +++ b/tests/integration/standalone/rclone/sops-nix.nix @@ -28,12 +28,9 @@ in uid = 1000; }; - systemd.tmpfiles.settings.age.rules."/home/alice/age-key".f = { - mode = "400"; - user = "alice"; - group = "users"; - argument = ageKey; - }; + systemd.tmpfiles.rules = [ + "f /home/alice/age-key 400 alice users - ${ageKey}" + ]; home-manager.users.alice = { config, ... }: diff --git a/tests/modules/misc/tmpfiles/basic-rules.nix b/tests/modules/misc/tmpfiles/basic-rules.nix deleted file mode 100644 index 68b00f87b..000000000 --- a/tests/modules/misc/tmpfiles/basic-rules.nix +++ /dev/null @@ -1,26 +0,0 @@ -{ - imports = [ ./common-stubs.nix ]; - - systemd.user.tmpfiles.settings = { - cache.rules."%C".d.age = "4 weeks"; - myTool.rules."%h/.config/myTool.conf"."f+" = { - mode = "0644"; - user = "alice"; - group = "users"; - argument = "my unescaped config"; - }; - }; - - nmt.script = '' - assertPathNotExists home-files/.config/user-tmpfiles.d/home-manager-purge-on-change.conf - - cacheRulesFile=home-files/.config/user-tmpfiles.d/home-manager-cache.conf - assertFileExists $cacheRulesFile - assertFileRegex $cacheRulesFile "^'d' '%C' '-' '-' '-' '4 weeks' $" - - myToolRulesFile=home-files/.config/user-tmpfiles.d/home-manager-myTool.conf - assertFileExists $myToolRulesFile - assertFileRegex $myToolRulesFile \ - "^'f+' '%h/.config/myTool.conf' '0644' 'alice' 'users' '-' my\\\\x20unescaped\\\\x20config$" - ''; -} diff --git a/tests/modules/misc/tmpfiles/common-stubs.nix b/tests/modules/misc/tmpfiles/common-stubs.nix deleted file mode 100644 index abc4c732d..000000000 --- a/tests/modules/misc/tmpfiles/common-stubs.nix +++ /dev/null @@ -1,3 +0,0 @@ -{ - test.stubs.systemd.outPath = null; -} diff --git a/tests/modules/misc/tmpfiles/default.nix b/tests/modules/misc/tmpfiles/default.nix deleted file mode 100644 index 608796fe9..000000000 --- a/tests/modules/misc/tmpfiles/default.nix +++ /dev/null @@ -1,7 +0,0 @@ -{ - tmpfiles-no-rules = ./no-rules.nix; - tmpfiles-basic-rules = ./basic-rules.nix; - tmpfiles-rules-with-purging = ./rules-with-purging.nix; - - tmpfiles-escaped-argument-warning = ./escaped-argument-warning.nix; -} diff --git a/tests/modules/misc/tmpfiles/escaped-argument-warning.nix b/tests/modules/misc/tmpfiles/escaped-argument-warning.nix deleted file mode 100644 index 0a7441b7e..000000000 --- a/tests/modules/misc/tmpfiles/escaped-argument-warning.nix +++ /dev/null @@ -1,14 +0,0 @@ -{ - imports = [ ./common-stubs.nix ]; - - systemd.user.tmpfiles.settings.foo.rules.path.f.argument = "my\\x20unescaped\\x20config"; - - test.asserts.warnings.expected = [ - '' - The 'systemd.user.tmpfiles.settings.foo.rules.path.f.argument' option - appears to contain escape sequences, which will be escaped again. - Unescape them if this is not intended. The assigned string is: - "my\x20unescaped\x20config" - '' - ]; -} diff --git a/tests/modules/misc/tmpfiles/no-rules.nix b/tests/modules/misc/tmpfiles/no-rules.nix deleted file mode 100644 index 8c8f0e77e..000000000 --- a/tests/modules/misc/tmpfiles/no-rules.nix +++ /dev/null @@ -1,9 +0,0 @@ -{ - imports = [ ./common-stubs.nix ]; - - systemd.user.tmpfiles.settings = { }; - - nmt.script = '' - assertPathNotExists home-files/.config/user-tmpfiles.d/ - ''; -} diff --git a/tests/modules/misc/tmpfiles/rules-with-purging.nix b/tests/modules/misc/tmpfiles/rules-with-purging.nix deleted file mode 100644 index a453bfcb6..000000000 --- a/tests/modules/misc/tmpfiles/rules-with-purging.nix +++ /dev/null @@ -1,28 +0,0 @@ -{ - imports = [ ./common-stubs.nix ]; - - systemd.user.tmpfiles.settings = { - cache.rules."%C".d.age = "4 weeks"; - myTool = { - rules = { - "%h/.config/myTool.conf"."f+".argument = "my_config"; - "%h/.config/myToolPurged.conf"."f+$".argument = "my_config_purged"; - }; - purgeOnChange = true; - }; - }; - - nmt.script = '' - cacheRulesFile=home-files/.config/user-tmpfiles.d/home-manager-cache.conf - assertFileExists $cacheRulesFile - assertFileRegex $cacheRulesFile "^'d' '%C' '-' '-' '-' '4 weeks' $" - - assertPathNotExists home-files/.config/user-tmpfiles.d/home-manager-myTool.conf - myToolRulesFile=home-files/.config/user-tmpfiles.d/home-manager-purge-on-change.conf - assertFileExists $myToolRulesFile - assertFileRegex $myToolRulesFile \ - "^'f+' '%h/.config/myTool.conf' '-' '-' '-' '-' my_config$" - assertFileRegex $myToolRulesFile \ - "^'f+$' '%h/.config/myToolPurged.conf' '-' '-' '-' '-' my_config_purged$" - ''; -}